<!DOCTYPE html>
<html lang="zh-CN">
<head hexo-theme='https://github.com/volantis-x/hexo-theme-volantis/tree/4.1.5'>
  <meta charset="utf-8">
  <!-- SEO相关 -->
  
    
  
  <!-- 渲染优化 -->
  <meta http-equiv='x-dns-prefetch-control' content='on' />
  <link rel='dns-prefetch' href='https://cdn.jsdelivr.net'>
  <link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
  <meta name="renderer" content="webkit">
  <meta name="force-rendering" content="webkit">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
  <meta name="HandheldFriendly" content="True" >
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

  <!-- 页面元数据 -->
  
  <title>翻译-SGX Programming Model - Schenk - Blog</title>
  
    <meta name="keywords" content="sgx">
  

  
    <meta name="description" content="论文Intel SGX Explained中的SGX Programming Model部分翻译">
  

  <!-- feed -->
  

  <!-- import meta -->
  

  <!-- link -->
  
    <link rel="shortcut icon" type='image/x-icon' href="https://cdn.jsdelivr.net/gh/Schenk75/Source/logos/steroids.svg">
  

  <!-- import link -->
  

  
    
<link rel="stylesheet" href="/css/first.css">

  

  
  <link rel="stylesheet" href="/css/style.css" media="print" onload="this.media='all';this.onload=null">
  <noscript><link rel="stylesheet" href="/css/style.css"></noscript>
  

  <script id="loadcss"></script>

</head>

<body>
  

<header id="l_header" class="l_header auto shadow blur floatable show" style='opacity: 0' >
  <div class='container'>
  <div id='wrapper'>
    <div class='nav-sub'>
      <p class="title"></p>
      <ul class='switcher nav-list-h m-phone' id="pjax-header-nav-list">
        <li><a id="s-comment" class="fas fa-comments fa-fw" target="_self" href='javascript:void(0)'></a></li>
        
          <li><a id="s-toc" class="s-toc fas fa-list fa-fw" target="_self" href='javascript:void(0)'></a></li>
        
      </ul>
    </div>
		<div class="nav-main">
      
        
        <a class="title flat-box" target="_self" href='/'>
          
            <img no-lazy class='logo' src='https://cdn.jsdelivr.net/gh/Schenk75/Source@latest/logos/taiga.svg'/>
          
          
          
        </a>
      

			<div class='menu navigation'>
				<ul class='nav-list-h m-pc'>
          
          
          
            
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/
                  
                  
                  
                    id="home"
                  >
                  <i class='fab fa-stack-overflow fa-fw'></i>主页
                </a>
                
              </li>
            
          
          
            
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/categories/
                  
                  
                  
                    id="categories"
                  >
                  <i class='fas fa-folder-open fa-fw'></i>分类
                </a>
                
              </li>
            
          
          
            
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/tags/
                  
                  
                  
                    id="tags"
                  >
                  <i class='fas fa-tags fa-fw'></i>标签
                </a>
                
              </li>
            
          
          
            
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/archives/
                  
                  
                  
                    id="archives"
                  >
                  <i class='fas fa-archive fa-fw'></i>归档
                </a>
                
              </li>
            
          
          
            
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/friends/
                  
                  
                  
                    id="friends"
                  >
                  <i class='fas fa-link fa-fw'></i>友链
                </a>
                
              </li>
            
          
          
            
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/about/
                  
                  
                  
                    id="about"
                  >
                  <i class='fas fa-info-circle fa-fw'></i>关于
                </a>
                
              </li>
            
          
          
				</ul>
			</div>

      <div class="m_search">
        <form name="searchform" class="form u-search-form">
          <i class="icon fas fa-search fa-fw"></i>
          <input type="text" class="input u-search-input" placeholder="Search..." />
        </form>
      </div>

			<ul class='switcher nav-list-h m-phone'>
				
					<li><a class="s-search fas fa-search fa-fw" target="_self" href='javascript:void(0)'></a></li>
				
				<li>
          <a class="s-menu fas fa-bars fa-fw" target="_self" href='javascript:void(0)'></a>
          <ul class="menu-phone list-v navigation white-box">
            
              
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/
                  
                  
                  
                    id="home"
                  >
                  <i class='fab fa-stack-overflow fa-fw'></i>主页
                </a>
                
              </li>
            
          
            
              
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/categories/
                  
                  
                  
                    id="categories"
                  >
                  <i class='fas fa-folder-open fa-fw'></i>分类
                </a>
                
              </li>
            
          
            
              
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/tags/
                  
                  
                  
                    id="tags"
                  >
                  <i class='fas fa-tags fa-fw'></i>标签
                </a>
                
              </li>
            
          
            
              
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/archives/
                  
                  
                  
                    id="archives"
                  >
                  <i class='fas fa-archive fa-fw'></i>归档
                </a>
                
              </li>
            
          
            
              
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/friends/
                  
                  
                  
                    id="friends"
                  >
                  <i class='fas fa-link fa-fw'></i>友链
                </a>
                
              </li>
            
          
            
              
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/about/
                  
                  
                  
                    id="about"
                  >
                  <i class='fas fa-info-circle fa-fw'></i>关于
                </a>
                
              </li>
            
          
            
          </ul>
        </li>
			</ul>
		</div>
	</div>
  </div>
</header>

  <div id="l_body">
    <div id="l_cover">
  
    
        <div id="full" class='cover-wrapper post dock' style="display: none;">
          
            <div class='cover-bg lazyload placeholder' data-bg="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/common/interstellar.jpg"></div>
          
          <div class='cover-body'>
  <div class='top'>
    
    
      <p class="title">Schenk - Blog</p>
    
    
      <p class="subtitle">SJTUer | Cuber</p>
    
  </div>
  <div class='bottom'>
    <div class='menu navigation'>
      <div class='list-h'>
        
          
            <a href="/categories/"
              
              
              id="categories">
              <i class='fas fa-folder-open fa-fw'></i><p>分类</p>
            </a>
          
            <a href="/tags/"
              
              
              id="tags">
              <i class='fas fa-tags fa-fw'></i><p>标签</p>
            </a>
          
            <a href="/archives/"
              
              
              id="archives">
              <i class='fas fa-archive fa-fw'></i><p>归档</p>
            </a>
          
            <a href="/friends/"
              
              
              id="friends">
              <i class='fas fa-link fa-fw'></i><p>友链</p>
            </a>
          
            <a href="/about/"
              
              
              id="about">
              <i class='fas fa-info-circle fa-fw'></i><p>关于</p>
            </a>
          
        
      </div>
    </div>
  </div>
</div>

          <div id="scroll-down" style="display: none;"><i class="fa fa-chevron-down scroll-down-effects"></i></div>
        </div>
    
  
  </div>

    <div id='safearea'>
      <div class='body-wrapper' id="pjax-container">
        

<div class='l_main'>
  <article class="article post white-box reveal md shadow floatable article-type-post" id="post" itemscope itemprop="blogPost">
  


  
  <div class="article-meta" id="top">
    
    
    
      <h1 class="title">
        翻译-SGX Programming Model
      </h1>
      <div class='new-meta-box'>
        
          
            
<div class='new-meta-item author'>
  <a class='author' href="/" rel="nofollow">
    <img no-lazy src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/common/avatar.jpg">
    <p>Schenk</p>
  </a>
</div>

          
        
          
            
  <div class='new-meta-item category'>
    <a class='notlink'>
      <i class="fas fa-folder-open fa-fw" aria-hidden="true"></i>
      <a class="category-link" href="/categories/Paper-Reading/">Paper Reading</a><span class="sep"></span><a class="category-link" href="/categories/SGX/">SGX</a>
    </a>
  </div>


          
        
          
            <div class="new-meta-item date" itemprop="dateUpdated" datetime="2022-04-28T15:45:12+08:00">
  <a class='notlink'>
    <i class="fas fa-edit fa-fw" aria-hidden="true"></i>
    <p>更新于：2022年4月28日</p>
  </a>
</div>

          
        
          
            
  <div class="new-meta-item wordcount">
    <a class='notlink'>
      <i class="fas fa-keyboard fa-fw" aria-hidden="true"></i>
      <p>字数：22.2k字</p>
    </a>
  </div>
  <div class="new-meta-item readtime">
    <a class='notlink'>
      <i class="fas fa-hourglass-half fa-fw" aria-hidden="true"></i>
      <p>时长：79分钟</p>
    </a>
  </div>


          
        
          
            
  <div class="new-meta-item browse leancloud">
    <a class='notlink'>
      
      <div id="lc-pv" data-title="翻译-SGX Programming Model" data-path="/2020/11/28/paper-reading/translation-sgx-programming-model/">
        <i class="fas fa-eye fa-fw" aria-hidden="true"></i>
        <span id='number'><i class="fas fa-circle-notch fa-spin fa-fw" aria-hidden="true"></i></span>
        次浏览
      </div>
    </a>
  </div>


          
        
      </div>
    
  </div>


  
  <p><strong>放在博客上链接不能跳转指定标题，暂时没有办法解决</strong></p>
<p>SGX的核心概念是enclave，一个受保护的环境，其中包含与安全敏感计算相关的代码和数据。</p>
<p>启用sgx的处理器通过将每个enclave的环境与enclave外部的不受信任的软件隔离，并实现允许远程方对运行在enclave内部的软件进行身份验证的软件认证方案，从而提供可信的计算。SGX的隔离机制旨在保护在enclave内执行的计算的机密性和完整性，防止来自同一台计算机上的恶意软件的攻击，以及一小部分物理攻击。</p>
<h2 id="5-1-SGX物理内存组织">5.1 SGX物理内存组织</h2>
<p>Enclave的代码和数据存储在 <em>Processor Reserved Memory</em> (PRM)中，PRM是DRAM的一个子集，不能被其他软件(包括系统软件和SMM代码)直接访问。CPU的集成内存控制器也拒绝针对PRM的DMA传输，从而保护它不被其他外设访问。</p>
<p><strong>PRM是一个连续的内存范围</strong>，其边界使用一个基地址和一个掩码寄存器来决定，与可变内存类型范围具有相同的语义。因此，PRM的大小必须是2的整数次幂，并且它的起始地址必须对齐到同样的2次幂。由于这些限制，检查一个地址是否属于PRM可以在硬件上很容易地完成。</p>
<h3 id="5-1-1-Enclave页面缓存-EPC">5.1.1 Enclave页面缓存 (EPC)</h3>
<p>Enclave的内容和相关的数据结构存储在Enclave页面缓存(EPC)中，EPC是PRM的一个子集，PRM是DRAM的一个连续范围，<strong>不能被系统软件或外设访问</strong>。如图所示：</p>
<p><img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201127221918953.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201127221918953.png" srcset="" alt="image-20201127221918953"></p>
<p>SGX设计支持同时在一个系统上有多个enclaves，这在多进程环境中是必需的。这是通过将EPC拆分为4 KB的页面来实现的，这些页面可以分配给不同的enclaves。EPC使用与体系结构的地址转换特性相同的页面大小。</p>
<p>EPC由管理计算机其余物理内存的系统软件管理。系统软件(可以是管理程序或操作系统内核)使用SGX指令将未使用的页面分配给enclaves，并释放之前分配的EPC页面。系统软件将向应用程序软件公开enclave创建和管理服务。</p>
<p>非enclave软件不能直接访问EPC，因为它包含在PRM中。这一限制在SGX的enclave隔离保证中发挥了关键作用，但当系统软件需要将初始代码和数据加载到新创建的enclave中时，就会造成障碍。SGX通过<strong>将分配EPC页面的指令同时也用作初始化页面的指令</strong>来解决这个问题。大多数EPC页是通过从非PRM内存页复制数据来初始化的。</p>
<h3 id="5-1-2-Enclave页面缓存映射表-EPCM">5.1.2 Enclave页面缓存映射表 (EPCM)</h3>
<p>SGX设计期望系统软件将EPC页面分配给enclave。然而，由于系统软件不受信任，SGX处理器会检查系统软件分配的正确性，并拒绝执行任何危及SGX安全保证的行为。例如，如果系统软件试图将相同的EPC页面分配给两个enclaves，则执行分配的SGX指令将失败。</p>
<p>为了执行安全检查，SGX在 <em>Enclave Page Cache Map</em> (EPCM)中记录一些关于系统软件对每个EPC页面的分配决策的信息。EPCM是一个数组，每个EPC页面对应一个条目，因此计算页面的EPCM入口地址只需要逐位移位操作和加操作。</p>
<p>EPCM的内容仅用于SGX的安全检查。在正常操作下，EPCM不会产生任何软件可见的行为，因而enclave作者和系统软件开发人员基本上可以忽略它。因此，SDM (<em>Intel’s Software Developer Manual</em>) 只在非常高的级别上描述EPCM，列出其中包含的信息，并指出EPCM是“受信任的内存”，而没有公开EPCM使用的存储介质或内存布局。</p>
<p>EPCM使用下表中的字段来跟踪每个EPC页面的所有权。我们将EPCM的完整讨论推迟到后面的部分，因为它的内容与SGX的所有特征紧密结合，这将在接下来的几节中进行描述。</p>
<img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201127223653632.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201127223653632.png" srcset="" alt="image-20201127223653632" style="zoom:80%;" />
<p>分配EPC页的SGX指令将相应EPCM条目的VALID位设置为1，并<strong>拒绝对已经设置了VALID位的EPC页进行操作</strong>。</p>
<p>分配EPC页的指令还决定了该页的预期用途，该用途记录在相应EPCM条目的 <em>page type</em> (PT)字段中。存储enclave代码和数据的页面被认为具有常规类型 <code>PT_REG</code> 。专用于存储SGX支持数据结构的页面被标记为特殊类型。例如，<code>PT_SECS</code> 类型标识保存SGX Enclave控制结构的页面，这将在下一节中进行描述。其他EPC页面类型将在以后的章节中描述。</p>
<p>最后，页面的EPCM条目还标识拥有EPC页面的enclave，此字段防止一个enclave访问另一个enclave的私有信息。由于EPCM为每个EPC页面标识一个具有所有权的enclave，因此enclave不可能使用EPC页面通过共享内存进行通信。幸运的是，enclaves可以共享不可信的非EPC内存，这将在<a href="#5.2.3-SGX-Enclaves%E5%9C%B0%E5%9D%80%E8%BD%AC%E6%8D%A2">§5.2.3</a>中讨论。</p>
<h3 id="5-1-3-SGX-Enclave控制结构-SECS">5.1.3 SGX Enclave控制结构 (SECS)</h3>
<p>SGX将每个Enclave元数据存储在与每个Enclave关联的 <em>SGX Enclave Control Structure</em> (SECS)中。<strong>每个SECS存储在专用的EPC页面中，页面类型为 <code>PT_SECS</code></strong>。</p>
<p>Enclave的身份几乎等同于它的SECS。启用enclave的第一步是分配EPC页面作为enclave的SECS，销毁enclave的最后一步是释放保存其SECS的页面。标识拥有EPC页面的enclave的EPCM入口字段指向enclave的SECS。当调用SGX指令时，系统软件使用SECS的虚拟地址来识别enclave。</p>
<p>所有SGX指令都以虚拟地址作为输入。由于SGX指令使用SECS地址来标识enclave，系统软件必须在其页表中创建指向其管理的enclave的SECS的条目。但是，系统软件不能访问任何SECS页面，因为这些页面存储在PRM中。<strong>SECS页面不会映射到它们的enclave的虚拟地址空间中，并且启用SGX的处理器显式地阻止enclave代码访问SECS页面</strong>。</p>
<p>这个看似随意的限制是为了使SGX实现能够在SECS内存储敏感信息，并且能够假定没有潜在的恶意软件能够访问该信息。例如，SDM声明<strong>每个enclave的度量都存储在SECS中</strong>。如果软件能够修改enclave的测量，SGX的软件认证方案将不能提供安全保证。</p>
<h2 id="5-2-SGX-Enclave的内存布局">5.2 SGX Enclave的内存布局</h2>
<p>SGX的设计目的是尽量减少[转换应用程序代码以利用enclave的优势]所需要的工作。历史表明，这是一个明智的决定，因为在英特尔架构的持续主导地位中，一个很大的因素是它保持向后兼容性的能力。为此，SGX  enclave在概念上与领先的软件模块化结构，动态加载库类似。动态加载库在Unix上打包为.so文件，在Windows上打包为.dll文件。</p>
<p>为简单起见，我们描述enclave和非enclave软件之间的交互时假设每个enclave都由一个应用程序进程使用，我们将其称为enclave的主进程。但是，我们注意到SGX的设计并没有明确地禁止多个应用程序进程共享一个enclave。</p>
<h3 id="5-2-1-Enclave线性地址范围-ELRANGE">5.2.1 Enclave线性地址范围 (ELRANGE)</h3>
<p>每个enclave在其虚拟地址空间中指定一个区域，称为 <em>Enclave Linear Address Range</em> (ELRANGE)，该区域用于映射存储在enclave的EPC页面中的代码和敏感数据。ELRANGE之外的虚拟地址空间用于访问主进程的其他内存，内存映射使用系统软件管理的页表建立。如下图所示:</p>
<img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128133153701.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128133153701.png" srcset="" alt="image-20201128133153701" style="zoom:100%;" />
<p>SGX的设计保证了enclave在ELRANGE内部的内存访问服从虚拟内存抽象，而在ELRANGE外部的内存访问没有得到保证。因此，<strong>enclave必须将其所有代码和私有数据存储在ELRANGE内，并且必须将ELRANGE外的内存视为对外部世界的不可信接口</strong>。</p>
<p>ELRANGE中的“线性”一词指的是64位Intel架构中残余分割特性产生的线性地址。在大多数情况下，“linear”可以看作是“virtual”的同义词。</p>
<p>ELRANGE在enclave的SECS(<a href="#5.1.3-SGX-Enclave%E6%8E%A7%E5%88%B6%E7%BB%93%E6%9E%84-(SECS)">§5.1.3</a>)中使用一个基地址(BASEADDR字段)和一个大小(SIZE)来指定。ELRANGE必须满足与可变内存类型范围和PRM范围(<a href="#5.1-SGX%E7%89%A9%E7%90%86%E5%86%85%E5%AD%98%E7%BB%84%E7%BB%87">§5.1</a>)相同的约束条件，即<strong>大小必须是2的幂，并且基地址必须与大小对齐</strong>。有了这些限制，SGX实现就可以容易地检查一个地址是否属于enclave的ELRANGE，无论是在硬件上还是在软件上。</p>
<p>非enclave软件不能访问PRM内存。<strong>在PRM内部的内存访问将导致一个中断</strong>，该中断在体系结构级别上未定义，在当前处理器上，中断的写将被忽略，中断的读将返回一个所有bit都设置为1的值。在上面描述的场景中，这就发挥了作用，在该场景中，enclave作为动态加载的库加载到主应用程序进程中。该系统软件将ELRANGE中的enclave代码和数据映射到EPC页面。如果应用程序软件试图访问ELRANGE内的内存，它将经历中断原语。当前原语不会导致应用程序崩溃(例如，由于页面错误)，但也保证了主应用程序不能篡改enclave或读取其私有信息。</p>
<h3 id="5-2-2-SGX-Enclave属性">5.2.2 SGX Enclave属性</h3>
<p>Enclave的执行环境很大程度上受到enclave  SECS(<a href="#5.1.3-SGX-Enclave%E6%8E%A7%E5%88%B6%E7%BB%93%E6%9E%84-(SECS)">§5.1.3</a>)中ATTRIBUTES字段的值的影响。此工作的其余部分会将ATTRIBUTES字段的子字段(如下表所示)称为enclave属性。</p>
<img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128135622106.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128135622106.png" srcset="" alt="image-20201128135622106" style="zoom:80%;" />
<p>从安全性的角度来看，最重要的属性是 DEBUG 标志。设置此标志后，将启用SGX的调试特性用于此enclave。这些调试特性包括<strong>读取和修改大部分enclave内存的能力</strong>。因此，调试应该只在开发环境中设置，因为这会导致enclave失去SGX的所有安全性保证。</p>
<p>SGX保证enclave代码将始终在XCR0寄存器设置为 <em>extended features request mask</em> (XFRM) 所指示的值的情况下运行。Enclave作者希望使用XFRM指定用于生成Enclave代码的编译器所支持的一组架构扩展。<strong>显式地指定XFRM允许Intel设计新的架构扩展来改变现有指令的语义</strong>，比如内存保护扩展(MPX)，而不必担心在开发时没有注意到新特性的enclave代码的安全影响。</p>
<p><strong>对于使用64位Intel架构的enclave,  MODE64BIT标志设置为true</strong>。从安全的角度来看，这个标志甚至不应该存在，因为支持次要架构会给SGX实现增加不必要的复杂性，并增加安全漏洞潜入的可能性。32位架构支持很可能是由于英特尔提供广泛向后兼容性的策略，到目前为止，这一策略取得了相当好的效果。清除MODE64BIT标志位可能会造成SGX漏洞，有待研究人员研究。</p>
<p>最后，<strong>在创建enclave的SECS时，INIT标志总是为false</strong>。这个标志在enclave生命周期的某个时刻被设置为true，这将在<a href="#5.3-SGX-Enclave%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F">§5.3</a>中进行总结。</p>
<h3 id="5-2-3-SGX-Enclaves地址转换">5.2.3 SGX Enclaves地址转换</h3>
<p>在SGX下，操作系统和管理程序仍然完全控制页表和EPT，每个enclave的代码使用和其主应用程序相同的地址转换过程和页表。这将使向现有系统软件添加SGX支持所需的更改量最小化。同时，让不受信任的系统软件管理页表会使SGX遭受§3.7所述的地址转换攻击。正如未来章节将揭示的，SGX设计的复杂性很大程度上可以归因于防止这些攻击的需要。</p>
<p>SGX的主动内存映射攻击防御机制围绕着确保每个EPC页面只能映射到一个特定的虚拟地址。<strong>在分配EPC页面时，它的预期虚拟地址记录在该页面的EPCM条目的ADDRESS字段中</strong>。</p>
<p>当地址转换的结果是EPC页面的物理地址时，CPU保证提供给地址转换过程的虚拟地址与页面EPCM条目中记录的预期虚拟地址相匹配。</p>
<p>通过确保每个EPC页面的访问权限始终与enclave作者的意图相匹配，SGX还可以防止一些被动内存映射攻击和故障注入攻击。<strong>每个EPC页面的访问权限是在分配页面时指定的，并记录在页面EPCM条目中的可读®、可写(W)和可执行(X)字段中</strong>，如下表所示:</p>
<img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128143209413.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128143209413.png" srcset="" alt="image-20201128143209413" style="zoom:80%;" />
<p>当一个地址转换解析为一个EPC页面时，相应的EPCM条目的字段将覆盖页表中指定的访问权限属性。例如，EPCM条目中的W字段覆盖可写(W)属性，而X字段覆盖禁用执行(XD)属性。</p>
<p>因此，<strong>enclave作者必须在包含enclave的同时包含内存布局信息，这样，加载enclave的系统软件将知道每个enclave页面的预期虚拟内存地址和访问权限</strong>。作为回报，SGX设计向enclave作者保证，管理页表和EPT的系统软件将不能以与作者预期不一致的方式设置enclave的虚拟地址空间。</p>
<p>.so和.dll文件格式是SGX打算使用的enclave运载工具，它们已经规定了指定软件模块要使用的虚拟地址，以及模块的每个内存区域所需的访问权限。</p>
<p>最后，<strong>启用SGX的CPU将确保ELRANGE内的虚拟内存(<a href="#5.2.1-Enclave%E7%BA%BF%E6%80%A7%E5%9C%B0%E5%9D%80%E8%8C%83%E5%9B%B4-(ELRANGE)">§5.2.1</a>)映射到EPC页面</strong>。这可以防止系统软件执行地址转换攻击，即将enclave的整个虚拟地址空间映射到PRM之外的DRAM页面，而PRM不会触发上述任何检查，系统软件可以直接访问这些页面。</p>
<h3 id="5-2-4-线程控制结构-TCS">5.2.4 线程控制结构 (TCS)</h3>
<p>SGX设计完全采用多核处理器。<strong>多个逻辑处理器可以通过不同的线程同时并发地执行同一个enclave的代码</strong>。</p>
<p>SGX实现为执行enclave代码的每个逻辑处理器使用一个 <em>Thread Control Structure</em> (TCS)。因此，enclave的作者必须至少提供与enclave支持的最大并发线程数相同的TCS实例。</p>
<p><strong>每个TCS存储在专用的EPC页面中，其EPCM条目类型为 <code>PT_TCS</code></strong>。SDM描述了TCS中的前几个字段。这些字段被认为属于结构的体系结构部分，因此保证在所有支持SGX的处理器上具有相同的语义。</p>
<p><strong>保存TCS的EPC页面的内容不能直接访问，甚至不能由拥有TCS的enclave的代码访问</strong>。此限制类似于对访问持有SECS实例的EPC页面的限制。但是，<strong>TCS中的体系结构字段可以通过enclave调试指令读取</strong>。TCS中的体系结构字段列出了逻辑处理器在执行非enclave代码和enclave代码之间转换时执行的上下文切换。例如，OENTRY字段指定当TCS用于开始执行enclave代码时加载在 指令指针(RIP) 中的值，因此enclave作者可以严格控制enclave的主程序可用的入口点。此外，OFSBASGX和OFSBASGX字段指定了加载在FS和GS段寄存器中的基地址，这通常指向 线程本地存储(TLS)。</p>
<h3 id="5-2-5-状态保存区域-SSA">5.2.5 状态保存区域 (SSA)</h3>
<p>当处理器在enclave中执行代码时遇到硬件异常，比如中断时，它会执行特权级别切换并调用系统软件提供的硬件异常处理程序。然而，在执行异常处理程序之前，处理器需要一个安全的区域来存储enclave代码的执行上下文，这样执行上下文中的信息就不会泄露给不受信任的系统软件。</p>
<p>在SGX设计中，<strong>在处理硬件异常时用于存储enclave线程的执行上下文的区域</strong>称为 <em>State Save Area</em> (SSA)，如下图所示（enclave虚拟地址空间的一种可能的布局。每个enclave有一个SECS，每个支持的并发线程有一个TCS。每个TCS指向一个SSA序列，并为RIP以及FS和GS的基址指定初始值）。<strong>每个TCS引用一个连续的SSA序列</strong>。SSA数组偏移量(OSSA)字段指定了第一个SSA在enclave的虚拟地址空间中的位置。SSA数量(NSSA)字段表示可用的SSAs的数量。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128145659924.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128145659924.png" srcset="" alt="image-20201128145659924"></p>
<p>每个SSA从EPC页面的开头开始，并<strong>使用在enclave的SECS的SSAFRAMESIZE字段中指定的EPC页面数量</strong>。通过减少需要处理的特殊情况的数量，这些对齐和大小限制很可能简化了SGX的实现。</p>
<p>Enclave线程的执行上下文由通用寄存器(GPRs)和XSAVE指令的结果组成。因此，执行上下文的大小取决于XSAVE使用的 请求特性位图(RFBM)。enclave中的所有代码都使用相同的RFBM，它是在XFRM  enclave属性(<a href="#5.2.2-SGX-Enclave%E5%B1%9E%E6%80%A7">§5.2.2</a>)中声明的。为每个SSA保留的EPC页面数量(在SSAFRAMESIZE中指定)必须足够大，以适合XSAVE输出的XFRM指定的特性位图。</p>
<p><strong>SSA存储在常规EPC页面中，其EPCM页面类型为 <code>PT_REG</code></strong>。因此，<strong>enclave软件可以访问SSA内容</strong>。SSA布局是体系结构的，并且完全记录在SDM中。这为主机应用程序在发生硬件异常后调用enclave异常处理程序并对SSA中的信息进行操作提供了可能性。</p>
<h2 id="5-3-SGX-Enclave的生命周期">5.3 SGX Enclave的生命周期</h2>
<p>Enclave的生命周期与资源管理密切相关，特别是EPC页面的分配。因此，在不同生命周期状态之间转换的指令只能由系统软件执行。系统软件将公开以下描述的SGX指令，作为enclave加载和销毁的服务。</p>
<p>下面的小节描述了enclave生命周期中的主要步骤，如下图所示:</p>
<img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128152622170.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128152622170.png" srcset="" alt="image-20201128152622170"  />
<h3 id="5-3-1-创建">5.3.1 创建</h3>
<p>当系统软件发出 <code>ECREATE</code> 指令时，一个enclave就诞生了，该指令<strong>将一个空闲的EPC页面转换为用于新enclave的SECS</strong>(<a href="#5.1.3-SGX-Enclave%E6%8E%A7%E5%88%B6%E7%BB%93%E6%9E%84-(SECS)">§5.1.3</a>)。</p>
<p><strong><code>ECREATE</code> 使用系统软件拥有的非EPC页面中的信息初始化新创建的SECS</strong>。这个页面指定SDM中定义的所有SECS字段的值，比如BASEADDR和SIZE，使用的是一种体系结构布局，未来的实现保证会保留这种布局。</p>
<p>虽然最初的SGX实现使用的实际SECS布局很可能与架构布局非常接近，但是未来的实现可以自由地改变这个布局，只要它们保持使用架构布局初始化SECS的能力。软件不能访问包含SECS的EPC页面，因此它不能依赖于SECS的内部布局。这是在虚拟机控制结构(VMCS，§2.8.3)中使用的更强的封装版本。</p>
<p><strong><code>ECREATE</code> 验证用于初始化SECS的信息，如果信息无效，则会导致页面错误(#PF，§2.8.2)或一般保护错误(#GP，§2.8.2)</strong>。例如，如果SIZE字段不是2的幂，<code>ECREATE</code>结果是#GP。这种验证，加上软件无法访问SECS这一事实，简化了其他SGX指令的实现，这可以假设SECS内部的信息是有效的。</p>
<p>最后，<strong><code>ECREATE</code>将enclave的INIT属性(<a href="#5.2.2-SGX-Enclave%E5%B1%9E%E6%80%A7">§5.2.2</a>)初始化为false值。在INIT属性被设置为true之前，enclave的代码不能执行</strong>，在初始化阶段会将INIT设置为true，这将在<a href="#5.3.3-%E5%88%9D%E5%A7%8B%E5%8C%96">§5.3.3</a>中描述。</p>
<h3 id="5-3-2-加载">5.3.2 加载</h3>
<p><code>ECREATE</code> 将新创建的SECS标记为未初始化。当enclave的SECS处于这种状态时，<strong>系统软件可以使用<code>EADD</code>指令将初始代码和数据加载到enclave中。EADD用于创建TCS页面(<a href="#5.2.4-%E7%BA%BF%E7%A8%8B%E6%8E%A7%E5%88%B6%E7%BB%93%E6%9E%84-(TCS)">§5.2.4</a>)和常规页面。</strong></p>
<p><strong><code>EADD</code> 从<em>页面信息</em>(PAGEINFO)结构中读取其输入数据</strong>，如下图所示。该结构的内容仅用于与SGX实现通信，因此它完全是体系结构的，并在SDM中记录。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128164130332.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128164130332.png" srcset="" alt="image-20201128164130332"></p>
<p>目前，PAGEINFO结构包含的将要分配的EPC页面的虚拟地址(LINADDR)、非EPC页面的虚拟地址（其内容将被复制到新分配的EPC页面(SRCPGE)）、一个虚拟地址，解析为SECS的enclave的页面(SECS)以及与新分配的EPC页面相关联的EPCM条目的某些字段的值(SECINFO)。</p>
<p>PAGEINFO结构中的SECINFO字段实际上是一个虚拟内存地址，并指向一个<em>安全信息</em>(SECINFO)结构，其中一些结构如上图所示。<strong>SECINFO结构包含新分配的EPC页面的访问权限(R、W、X)及其EPCM页面类型(<code>PT_REG</code>或<code>PT_TCS</code>)</strong>。与PAGEINFO一样，SECINFO结构仅用于与SGX实现通信数据，因此它的内容也完全是体系结构的。但是，该结构的大部分64字节内容被保留以备将来使用。</p>
<p>PAGEINFO和SECINFO结构都是由调用<code>EADD</code>指令的系统软件准备的，因此必须<strong>包含在非EPC页面中</strong>。两个结构必须按照其大小对齐——<strong>PAGEINFO是32字节长，因此每个PAGEINFO实例必须是32字节对齐的，而SECINFO是64字节对齐的，因此每个SECINFO实例必须是64字节对齐的</strong>。对齐要求可能通过减少必须处理的特殊情况的数量来简化SGX实现。</p>
<p><strong><code>EADD</code>在修改新分配的EPC页面或其EPCM条目之前验证其输入</strong>。最重要的是，<strong>尝试将一个页面<code>EADD</code>到SECS处于初始化状态（INIT属性为true）的enclave将导致一个#GP</strong>。此外，<strong>尝试<code>EADD</code>已经分配的EPC页面(其EPCM条目中的VALID字段为1)将导致一个#PF</strong>。EADD还确保页面的虚拟地址位于enclave的ELRANGE内，并且<strong>SECINFO中的所有保留字段都被设置为零</strong>。</p>
<ul>
<li><strong><code>EADD</code> 指令将EPCM条目中的VALID字段置为1？</strong></li>
</ul>
<p>在加载enclave时，系统软件还将使用EEXTEND指令，该指令将<strong>更新在软件认证过程中使用的enclave度量值</strong>。软件认证在<a href="#5.8-SGX%E8%BD%AF%E4%BB%B6%E8%AE%A4%E8%AF%81">§5.8</a>中进行了讨论。</p>
<h3 id="5-3-3-初始化">5.3.3 初始化</h3>
<p>在将初始代码和数据页面加载到enclave后，<strong>系统软件必须使用<em>Launch enclave</em>(LE)来获取EINIT令牌结构</strong>，这是通过一个未文档化的过程实现的，将在<a href="#5.9.1-Enclave%E5%B1%9E%E6%80%A7%E8%AE%BF%E9%97%AE%E6%8E%A7%E5%88%B6">§5.9.1</a>中详细描述。然后<strong>将令牌提供给<code>EINIT</code>指令，该指令将enclave的SECS标记为<em>initialized</em></strong>。</p>
<p>LE是由Intel提供的特权enclave，是使用由Intel以外的第三方编写的enclave的先决条件。LE是SGX的Enclave，因此必须使用本节中描述的进程来创建、加载和初始化它。然而，LE是用一个特殊的Intel密钥加密签名的(§3.1.3)，这个密钥被硬编码到SGX的实现中，这导致**<code>EINIT</code>在初始化LE时没有检查有效的EINIT令牌结构**。</p>
<p><strong>当<code>EINIT</code>成功完成时，它将enclave的INIT属性设置为true</strong>。这就为ring 3(§2.3)应用软件使用<a href="#5.4-SGX%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F">§5.4</a>中描述的SGX指令执行enclave的代码开辟了道路。另一方面，一旦INIT设置为true，就不能再在该enclave上调用<code>EADD</code>，因此<strong>在执行<code>EINIT</code>指令之前，系统软件必须加载构成enclave初始状态的所有页面</strong>。</p>
<h3 id="5-3-4-销毁">5.3.4 销毁</h3>
<p>Enclave完成了它要执行的计算之后，<strong>系统软件执行<code>EREMOVE</code>指令来释放enclave使用的EPC页面</strong>。</p>
<p><strong><code>EREMOVE</code>将EPC页面的EPCM条目的VALID字段设置为0，从而将该页面标记为可用</strong>。在释放页面之前，<code>EREMOVE</code>确保在拥有要删除的页面的enclave内部没有执行代码的逻辑处理器。</p>
<p>当保存SECS的EPC页面被释放时，enclave将被完全销毁。<strong>如果SECS页面被任何其他EPCM条目的ENCLAVESECS字段引用，<code>EREMOVE</code>将拒绝释放该页面</strong>，因此，<strong>只有在所有enclave页面都被释放之后，才能释放enclave的SECS页面</strong>。</p>
<h2 id="5-4-SGX线程的生命周期">5.4 SGX线程的生命周期</h2>
<p>在enclave被初始化(<a href="#5.3.3-%E5%88%9D%E5%A7%8B%E5%8C%96">§5.3.3</a>)和被销毁(<a href="#5.3.4-%E9%94%80%E6%AF%81">§5.3.4</a>)这段时间内，任何将enclave的EPC页面映射到其虚拟地址空间的应用程序进程都可以执行enclave的代码。</p>
<p>当在enclave中执行代码时，逻辑处理器被称为处于enclave模式，它执行的代码可以<strong>访问属于当前执行的enclave的常规(<code>PT_REG</code>，<a href="#5.1.2-Enclave%E9%A1%B5%E9%9D%A2%E7%BC%93%E5%AD%98%E6%98%A0%E5%B0%84%E8%A1%A8-(EPCM)">§5.1.2</a>)EPC页面</strong>。<strong>当逻辑进程处于enclave模式之外时，它会退回处理器保留内存范围(PRM, <a href="#5.1-SGX%E7%89%A9%E7%90%86%E5%86%85%E5%AD%98%E7%BB%84%E7%BB%87">§5.1</a>)内的任何内存访问，包括EPC</strong>。</p>
<p><strong>每个执行enclave代码的逻辑处理器都使用一个线程控制结构</strong>(TCS，<a href="#5.2.4-%E7%BA%BF%E7%A8%8B%E6%8E%A7%E5%88%B6%E7%BB%93%E6%9E%84-(TCS)">§5.2.4</a>)。当一个TCS被一个逻辑处理器使用时，它被认为是忙碌的，并且它不能被任何其他逻辑处理器使用。下图演示了主进程用于执行enclave代码的指令，以及它们与目标TCS的交互，这是具有两个状态保存区(SSAs)的SGX线程控制结构(TCS)的生命周期的各个阶段。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128174512721.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128174512721.png" srcset="" alt="image-20201128174512721"></p>
<p>假设没有发生硬件异常，enclave的主进程使用<a href="#5.4.1-%E5%90%8C%E6%AD%A5Enclave%E8%BF%9B%E5%85%A5">§5.4.1</a>中描述的<code>EENTER</code>指令来执行enclave代码。当enclave代码完成它的任务时，它使用EEXIT指令(参见<a href="#5.4.2-%E5%90%8C%E6%AD%A5Enclave%E9%80%80%E5%87%BA">§5.4.2</a>)将执行控制权返回给调用enclave的主进程。</p>
<p>如果在逻辑处理器处于enclave模式时发生硬件异常，则在调用系统软件的异常处理程序之前，使用<em>异步enclave退出</em>(AEX)将处理器从enclave模式中取出(<a href="#5.4.3-%E5%BC%82%E6%AD%A5Enclave%E9%80%80%E5%87%BA">§5.4.3</a>)。在系统软件的处理程序被调用后，enclave的主进程可以使用<a href="#5.4.4-%E4%BB%8E%E5%BC%82%E6%AD%A5%E9%80%80%E5%87%BA%E6%81%A2%E5%A4%8D">§5.4.4</a>中描述的<code>ERESUME</code>指令重新输入enclave并恢复它退出之前正在执行的计算。</p>
<h3 id="5-4-1-同步Enclave进入">5.4.1 同步Enclave进入</h3>
<p>在较高的级别上，<code>EENTER</code>执行受控跳转到enclave代码，同时执行SGX的安全保证所需要的处理器配置。遍历所有配置步骤是一项冗长乏味的工作，但这是理解SGX使用的所有数据结构如何协同工作的必要先决条件。由于这个原因，<code>EENTER</code>和它的兄弟指令描述的比其他的SGX指令更详细。</p>
<p>下图所示的<code>EENTER</code>只能由运行在Ring 3(§2.3)的非特权应用程序软件执行，<strong>如果由系统软件执行，则会导致未定义指令(#UD)故障</strong>。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128175647644.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128175647644.png" srcset="" alt="image-20201128175647644"></p>
<p><code>EENTER</code>将逻辑处理器切换到enclave模式，但<strong>不执行特权级别切换</strong>。因此，<strong>enclave代码总是在Ring3执行</strong>，具有与调用它的应用程序代码相同的特权。这使得基础设施所有者可以允许用户提供的软件创建和使用enclave，同时保证OS内核和管理员仍然可以保护基础设施免受bug或恶意软件的攻击。</p>
<p><strong><code>EENTER</code>将TCS的虚拟地址作为它的输入，并要求TCS可用，并且TCS中至少有一个状态保存区域(SSA，<a href="#5.2.5-%E7%8A%B6%E6%80%81%E4%BF%9D%E5%AD%98%E5%8C%BA%E5%9F%9F-(SSA)">§5.2.5</a>)可用</strong>。后者是通过确保TCS中的<em>当前SSA索引</em>(CSSA)字段小于SSAs  (NSSA)的数量来实现的。在执行enclave代码时发生硬件异常时，将使用CSSA所指示的SSA(应该称为当前SSA)。</p>
<p><strong><code>EENTER</code>将逻辑处理器转换为enclave模式，并将指令指针(RIP)设置为它接收到的TCS中的<em>入口点偏移</em>(OENTRY)字段所指示的值</strong>。<code>EENTER</code>被不受信任的调用者用来在一个受保护的环境中执行代码，因此它与用于调用系统软件的<code>SYSCALL</code>(§2.8)具有相同的安全考虑。将RIP设置为OENTRY所指示的值，可以向enclave作者保证enclave代码只会在定义良好的节点上调用，并防止恶意主机应用程序绕过enclave作者可能执行的任何安全检查。</p>
<p><strong><code>EENTER</code>还将XCR0(§2.6)设置为enclave属性的值XFRM</strong>(<a href="#5.2.2-SGX-Enclave%E5%B1%9E%E6%80%A7">§5.2.2</a>)，XCR0是一个寄存器，用于控制正在使用的扩展架构特性。确保XCR0是根据enclave作者的意图设置的，从而防止恶意操作系统通过启用enclave尚未准备处理的体系结构特性来绕过enclave的安全性。</p>
<p>此外，<code>EENTER</code>使用TCS中指定的值加载段寄存器(§2.7)FS和GS的基底。段的选择器和类型是硬编码为安全值的Ring3数据段。SGX设计的这个方面使得实现每个线程的<em>线程本地存储</em>(TLS)变得很容易。对于64位的enclave，这是一种方便的特性，而不是一种安全措施，因为enclave代码可以使用WRFSBASE和WRGSBASE指令安全地将新的基底加载到FS和GS中。</p>
<p><strong><code>EENTER</code>将备份它修改的寄存器的旧值，以便在enclave完成其计算时恢复这些值</strong>。就像<code>SYSCALL</code>一样，<code>EENTER</code>将以下指令的地址保存在RCX寄存器中。</p>
<p>有趣的是，SDM声明XCR0、FS和GS寄存器的旧值保存在专用于SGX实现的新寄存器中。但是，鉴于它们只用于enclave退出，我们希望寄存器保存在DRAM中，在TCS的保留区域中。</p>
<p>与<code>SYSCALL</code>一样，<strong><code>EENTER</code>不修改堆栈指针寄存器(RSP)</strong>。为了避免任何安全漏洞，<strong>enclave代码应该将RSP设置为指向完全包含在EPC页面中的堆栈区域</strong>。通过设置每个线程的TLS区域以包含指向线程堆栈的指针，并将RSP设置为通过读取FS或GS段所指向的TLS区域获得的值，多线程enclave可以很容易地实现每个线程的堆栈区域。</p>
<p>最后，当<code>EENTER</code>进入enclave模式时，它会暂停一些处理器的调试特性，比如硬件断点和精确的基于事件的采样(PEBS)。从概念上讲，附加到主进程的调试器将enclave的执行看作一条单处理器指令。</p>
<h3 id="5-4-2-同步Enclave退出">5.4.2 同步Enclave退出</h3>
<p><strong><code>EEXIT</code>只能在逻辑处理器处于enclave模式时执行，如果在任何其他情况下执行，则会导致一个(#UD)</strong>。简而言之，该指令<strong>将处理器返回到Ring3外部enclave模式，并恢复由<code>EENTER</code>保存的寄存器</strong>。</p>
<p>与<code>SYSRET</code>不同，<strong><code>EEXIT</code>在退出enclave模式后将RIP设置为从RBX读取的值</strong>。这与<code>EENTER</code>不一致，<strong><code>EENTER</code>将RIP值保存到RCX</strong>。除非这种不一致性源于SDM中的错误，否则enclave代码必须确保注意到这种差异。</p>
<p>SDM明确声明**<code>EEXIT</code>不会修改大多数寄存器**，因此enclave作者必须确保在将控制权返回给主进程之前清除存储在处理器寄存器中的任何秘密。此外，如果enclave软件没有将堆栈指针RSP和堆栈帧基指针RBP恢复到它们在<code>EENTER</code>被调用时拥有的值，那么它很可能会在调用者中造成错误。</p>
<p>Enclave代码可能会在其调用者中引发错误，这似乎很不幸。无论好坏，这完全符合应用程序调用动态加载模块的情况。更具体地说，模块的代码还负责保存与堆栈相关的寄存器，有bug的模块可能会跳转到主进程的应用程序代码中的任何位置。</p>
<p>本节描述64位enclave的<code>EENTER</code>行为。32位enclave的<code>EENTER</code>实现要复杂得多，因为在32位Intel架构中仍然存在成熟的分段模型引入了额外的特殊情况。</p>
<h3 id="5-4-3-异步Enclave退出">5.4.3 异步Enclave退出</h3>
<p>如果在逻辑处理器执行enclave代码时发生了硬件异常，比如错误(§2.8.2)或中断(§2.12)，处理器<strong>在调用系统软件的异常处理程序之前执行<em>异步enclave退出</em></strong>(AEX)，如下图所示：</p>
<p><img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128192836878.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128192836878.png" srcset="" alt="image-20201128192836878"></p>
<p>AEX保存enclave代码的执行上下文(§2.6)，恢复<code>EENTER</code>保存的状态，并设置处理器寄存器，以便系统软件的硬件异常处理程序返回到enclave的主进程中的异步退出处理程序。<strong>退出处理程序将使用<code>ERESUME</code>指令恢复被硬件异常中断的enclave计算</strong>。</p>
<p>除了<a href="#5.4.1-%E5%90%8C%E6%AD%A5Enclave%E8%BF%9B%E5%85%A5">§5.4.1</a>中描述的行为外，<code>EENTER</code>还向当前SSA写入一些信息，这只在AEX发生时使用。<code>EENTER</code>将堆栈指针寄存器RSP和堆栈帧基指针寄存器RBP存储到当前SSA中的U_RSP和U_RBP字段中。最后，<strong><code>EENTER</code>将值存储在当前SSA的<em>异步退出处理程序指针</em>(AEP)字段中的RCX中</strong>。</p>
<p>当在enclave模式下发生硬件异常时，SGX实现执行一系列步骤，将逻辑处理器从enclave模式中取出，并在系统软件中调用硬件异常处理程序。从概念上讲，SGX首先执行AEX以使逻辑处理器脱离enclave模式，然后使用§2.8.2中描述的标准英特尔架构的行为来处理硬件异常。实际的Intel处理器可能会将AEX实现与异常处理实现交错使用。然而，为了简单起见，该工作将AEX描述为一个单独的过程，在采取任何异常处理步骤之前执行。</p>
<p>在英特尔架构中，如果发生硬件异常，应用程序代码的执行上下文可以被系统软件的异常处理程序读取和修改(§2.8.2)。当应用程序软件信任系统软件时，这是可以接受的。然而，在SGX的威胁模式下，系统软件不被Enclave信任。因此，<strong>AEX步骤通过将其所有寄存器重置为预定义值来清除执行状态中可能存在的任何秘密</strong>。</p>
<p>在重置enclave的执行状态之前，将它备份到当前SSA中。具体来说，AEX备份SSA中的GPRSGX区域中的通用寄存器(GPRs，§2.6)，然后使用enclave SECS中的XFRM字段中指定的<em>请求特性位图</em>(RFBM)执行<code>XSAVE</code>(§2.6)。由于每个SSA都完全存储在分配给enclave的EPC页面中，因此<strong>系统软件不能读取或篡改备份的执行状态</strong>。<strong>当SSA接收到enclave的执行状态时，通过递增当前TCS中的CSSA字段，将其标记为used</strong>。</p>
<p>在清除执行上下文后，<strong>AEX进程将RSP和RBP设置为<code>EENTER</code>在当前SSA中保存的值，将RIP设置为当前SSA的AEP字段中的值</strong>。这样，当系统软件的硬件异常处理程序完成时，处理器将在enclave的主进程中执行异步退出处理程序代码。SGX的设计使得在包含<code>EENTER</code>指令的例程中将异步处理程序代码设置为异常处理程序变得很容易，因为RSP和RBP寄存器拥有的值与执行<code>EENTER</code>时相同。</p>
<p>AEX在enclave模式之外获取逻辑处理器所采取的许多操作都与<code>EEXIT</code>匹配。段寄存器FS和GS恢复到<code>EENTER</code>保存的值，所有被<code>EENTER</code>抑制的调试工具恢复到它们以前的状态。</p>
<h3 id="5-4-4-从异步退出恢复">5.4.4 从异步退出恢复</h3>
<p>当在enclave模式下发生硬件异常时，处理器在调用系统软件设置的异常处理程序之前执行AEX。AEX设置执行上下文的方式是，<strong>当系统软件完成异常处理时，它返回到enclave的主进程中的异步退出处理程序。异步异常处理程序通常执行<code>ERESUME</code>指令</strong>，这将导致逻辑处理器返回到enclave模式并继续被硬件异常中断的计算。</p>
<p><code>ERESUME</code>与<code>EENTER</code>共享其大部分功能。如下图所示：</p>
<p><img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128201539849.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128201539849.png" srcset="" alt="image-20201128201539849"></p>
<p><code>EENTER</code>和<code>ERESUME</code>接收相同的输入，即<a href="#5.4.1-%E5%90%8C%E6%AD%A5Enclave%E8%BF%9B%E5%85%A5">§5.4.1</a>中描述的TCS指针和<a href="#5.4.3-%E5%BC%82%E6%AD%A5Enclave%E9%80%80%E5%87%BA">§5.4.3</a>中描述的AEP指针。最常见的应用程序设计将每个<code>EENTER</code>实例与使用完全相同的参数调用<code>ERESUME</code>的异步退出处理程序配对。</p>
<p><code>ERESUME</code>和<code>EENTER</code>之间的主要区别在于前者使用一个由AEX“填写”的SSA(<a href="#5.4.3-%E5%BC%82%E6%AD%A5Enclave%E9%80%80%E5%87%BA">§5.4.3</a>)，而后者使用一个空的SSA。因此，<strong>如果提供的TCS中的CSSA字段为0，则<code>ERESUME</code>会导致#GP故障，而如果CSSA大于或等于NSSA，则<code>EENTER</code>会失败</strong>。</p>
<p>当成功时，<code>ERESUME</code>递减TCS的CSSA字段，并恢复TCS中CSSA字段所指向的SSA备份的执行上下文。具体来说，<strong><code>ERESUME</code>实现从SSA中的GPRSGX字段中恢复GPRs(§2.6)，并执行<code>XRSTOR</code>(§2.6)来加载与enclave所使用的扩展体系结构特性相关联的执行状态</strong>。</p>
<p><code>ERESUME</code>与<code>EENTER</code>共享以下行为。这两个指令<strong>都写入当前SSA中的U_RSP、U_RBP和AEP字段</strong>。这两个指令<strong>遵循相同的过程来备份XCR0、FS和GS段寄存器</strong>，并<strong>基于当前TCS及其enclave的SECS将它们设置为相同的值</strong>。最后，这两个指令都<strong>禁用了逻辑处理器的调试特性的同一子集</strong>。</p>
<p><code>ERESUME</code>正确处理的一个有趣的情况是，它在执行<code>XRSTOR</code>之前将XCR0设置为enclave属性XFRM  。<strong>如果SSA中的<em>请求特性位图</em>(RFBM)不是XFRM的子集，那么<code>ERESUME</code>就会失败</strong>。这很重要，因为尽管AEX总是使用XFRM值作为RFBM，但在另一个线程上执行的enclave代码可以在调用<code>ERESUME</code>之前自由地修改SSA内容。</p>
<p><code>ERESUME</code>实现中正确的操作顺序可以防止恶意应用程序使用enclave修改与未在XFRM中声明的扩展体系结构特性相关联的寄存器。这将破坏系统软件提供线程级执行上下文隔离的能力。</p>
<h2 id="5-5-EPC页面交换">5.5 EPC页面交换</h2>
<p>现代操作系统内核利用地址转换(§2.5)来实现页面交换，也称为分页(§2.5)。简而言之，通过将很少使用的内存页交换到称为磁盘等较慢的存储介质，分页允许OS内核过量提交计算机的DRAM。</p>
<p>分页是有效利用计算机资源的关键因素。例如，用户并发运行多个程序的桌面系统可以退出分配给不活动应用程序的内存页，而不会显著降低用户体验。</p>
<p>不幸的是，不能允许OS通过像在PRM范围之外的DRAM内存页面交换一样来交换enclave的EPC页面。在SGX的威胁模型中，enclave不相信系统软件，因此SGX的设计提供了一种EPC页面交换方法，可以防御试图进行§3.7中所述的任何主动地址转换攻击的恶意操作系统。</p>
<p>SGX所提供的安全性的代价是，支持交换EPC页面的操作系统内核必须使用经过修改的页面交换实现，该实现与SGX机制交互。Enclave作者基本上可以忽略EPC交换，类似于今天的应用程序开发人员可以忽略OS内核的分页实现。</p>
<p>如下图所示，SGX支持将EPC页面交换到PRM范围之外的DRAM页面。系统软件使用现有的页交换技术实现将这些页的内容从DRAM中交换到磁盘上。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128202326077.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128202326077.png" srcset="" alt="image-20201128202326077"></p>
<p>SGX的交换特性围绕着<code>EWB</code>指令，在<a href="#5.5.4-EPC%E9%A1%B5%E9%9D%A2%E4%BA%A4%E6%8D%A2">§5.5.4</a>中有详细描述。实际上，<strong><code>EWB</code>将EPC页交换到EPC之外的DRAM页，并通过将该页EPCM条目中的VALID字段置零将EPC页标记为可用</strong>。</p>
<p><strong>SGX设计依赖于对称密钥加密来保证被交换的EPC页面的机密性和完整性，并依赖于nonces(§3.1.4)来保证带回EPC的页面的新鲜度</strong>。这些nonces存储在<a href="#5.5.2-%E7%89%88%E6%9C%AC%E6%95%B0%E7%BB%84" title="VA">§5.5.2</a>中介绍的版本数组(VA)中，它们是专门用于nonce存储的EPC页面。</p>
<p>在EPC页面被交换并释放给其他enclaves使用之前，SGX实现必须确保没有TLB拥有与被交换的页面相关联的地址转换，以避免§3.7.4中描述的基于TLB的地址转换攻击。</p>
<p>正如<a href="#5.1.1-Enclave%E9%A1%B5%E9%9D%A2%E7%BC%93%E5%AD%98-(EPC)">§5.1.1</a>中所解释的，SGX让系统软件负责管理EPC。自然地，本节中描述的用于实现EPC分页的SGX指令只对运行在Ring 0(§2.3)上的系统软件可用。</p>
<p>在今天的软件栈(§2.3)中，只有操作系统内核实现了页面交换，以支持DRAM的过度使用。管理程序仅用于在操作系统之间划分计算机的物理资源。因此，在编写本节时，我们期望OS内核也将承担EPC页面交换的责任。为了简单起见，我们经常使用术语“操作系统内核”而不是“系统软件”。读者应该知道，SGX设计并不排除系统管理程序实现自己的EPC页面交换的系统。因此，“OS内核”实际上应该理解为“执行EPC分页的系统软件”。</p>
<h3 id="5-5-1-页面交换和TLB">5.5.1 页面交换和TLB</h3>
<p>SGX没有向内存执行单元添加任何安全检查(§2.9.4，§2.10)。相反，SGX的访问控制检查是在地址转换(§2.5)完成之后进行的，就在转换结果写入TLBs之前(§2.11.5)。在整个SDM中，这个方面通常被忽略，但是在解释SGX的EPC页面清除机制时，它就变得显而易见了。</p>
<p>关于SGX的内存访问保护检查的完整讨论值得单独一节来讨论，请参见§6.2。只需使用SGX的安全模型中的两个需求就可以解释EPC页面清除机制。首先，当一个逻辑处理器通过<code>EEXIT</code>(<a href="#5.4.2-%E5%90%8C%E6%AD%A5Enclave%E9%80%80%E5%87%BA">§5.4.2</a>)或AEX(<a href="#5.4.3%E5%BC%82%E6%AD%A5Enclave%E9%80%80%E5%87%BA">§5.4.3</a>)退出一个enclave时，它的TLB被刷新。其次，当从enclave释放EPC页面时，必须使得执行该enclave代码的所有逻辑处理器退出enclave。这足以保证删除任何针对已释放的EPC的TLB条目。</p>
<p>系统软件可以通过发送处理器间中断(IPI，§2.12)，使逻辑处理器退出enclave，这将在接收时触发AEX。</p>
<p>SGX不相信系统软件。因此，<strong>在将EPC页面的EPCM条目标记为free之前，SGX必须确保OS内核已经清除了所有可能包含该页面转换的TLB</strong>。此外，为每个页面清除执行IPIs和TLB刷新会给分页实现增加很大的开销，因此SGX设计允许使用一个IPI/TLB刷新序列来清除一批页面。</p>
<p><strong>TLB刷新验证逻辑依赖于一个名为BLOCKED的1位EPCM条目字段</strong>。如下图所示，VALID和BLOCKED字段产生三种可能的EPC页面状态。当两个位都为零时，页面是空闲的；当VALID为1，BLOCKED为0时使用；当两个位都为1时，页面被阻塞。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128202429126.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128202429126.png" srcset="" alt="image-20201128202429126"></p>
<p><strong>被阻塞的页面被认为不能被enclave访问</strong>。如果一个地址转换导致一个被阻塞的EPC页面，SGX实现会导致一个页面错误(#PF，§2.8.2)。这保证了一旦一个页面被阻塞，CPU将不会创建任何新的指向它的TLB条目。</p>
<p>此外，<strong>每条SGX指令都确保它所操作的EPC页面不会被阻塞</strong>。例如，<code>EENTER</code>确保它提供的TCS没有被阻塞，它的enclave的SECS没有被阻塞，以及当前SSA中的每个页面没有被阻塞。</p>
<p>为了交换一批EPC页面，OS内核必须首先针对这些页面发出<code>EBLOCK</code>指令。SGX还希望OS从页表中删除EPC页的映射，但并不信任OS。</p>
<p><strong>在所有所需的页面都被阻塞之后，操作系统内核必须执行一条<code>ETRACK</code>指令，这条指令指示SGX实现跟踪哪些逻辑处理器的TLBs已被刷新</strong>。<code>ETRACK</code>要求enclave的SECS(<a href="#5.1.3-SGX-Enclave%E6%8E%A7%E5%88%B6%E7%BB%93%E6%9E%84-(SECS)">§5.1.3</a>)的虚拟地址。如果OS希望交换属于多个enclave的一批EPC页面，它必须为每个enclave发出一个<code>ETRACK</code>。</p>
<p>按照<code>ETRACK</code>指令，操作系统内核必须在所有执行enclave代码的逻辑处理器上的enclave退出。SGX设计期望操作系统将使用IPIs在逻辑处理器中导致AEXs，而逻辑处理器的TLBs必须被刷新。</p>
<p>当OS对每个要交换的EPC页面执行<code>EWB</code>指令时，EPC页面交换过程就完成了。这条指令将在<a href="#5.5.4-EPC%E9%A1%B5%E9%9D%A2%E4%BA%A4%E6%8D%A2">§5.5.4</a>中详细描述，它将<strong>写入EPC页面的加密版本并将其交换到DRAM中，然后通过清除其EPCM条目中的VALID和BLOCKED位来释放该页面</strong>。在执行其任务之前，<code>EWB</code>确保它所目标的EPC页面已经被阻塞，并检查<code>ETRACK</code>设置的状态，以确保所有相关的TLB都已被刷新。</p>
<p>可以<strong>通过<code>ELDU</code>和<code>ELDB</code>指令将被交换的页面加载回EPC</strong>。这两个指令启动时都有一个空闲的EPC页面和一个DRAM页面(其中包含EPC页面的已交换内容)，然后将DRAM页面的内容解密到EPC页面，并恢复相应的EPCM条目。<strong><code>ELDU</code>和<code>ELDB</code>之间的唯一区别是后者在页面的EPCM条目中设置阻塞位，而前者将其清除</strong>。</p>
<p><code>ELDU</code>和<code>ELDB</code>类似于<code>ECREATE</code>和<code>EADD</code>，因为它们填充了一个空闲的EPC页面。由于他们操作的页面是空闲的，因此SGX安全模型断言没有TLB条目可能针对它。因此，这些指令不需要类似于<code>EBLOCK</code>或<code>ETRACK</code>的机制。</p>
<h3 id="5-5-2-版本数组-VA">5.5.2 版本数组 (VA)</h3>
<p>当<code>EWB</code>交换EPC的内容时，它会<strong>创建一个8字节的nonce</strong>(§3.1.4)，英特尔的文档将其称为页面版本。<strong>SGX的新鲜度保证是建立在安全存储nonces的假设之上的，所以<code>EWB</code>将它创建的nonce存储在一个<em>版本数组</em>(VA)中</strong>。</p>
<p>版本数组是EPC页面，专门用于存储由<code>EWB</code>生成的nonces。每个VA被划分成槽，每个槽都足够存储一个nonce。假设EPC页面的大小是4KB，每个nonce占用8字节，那么每个VA就有512个槽。</p>
<p><strong>页是使用<code>EPA</code>指令分配的，该指令接受空闲EPC页的虚拟地址，并将其转换为具有空槽的版本数组</strong>。每一页在其EPCM条目中以PT_VA类型标识。<strong>与SECS页面一样，VA页面的EPCM条目中的ENCLAVEADDRESS字段设置为零</strong>，任何软件(包括enclave)都不能直接访问它们。</p>
<p>与到目前为止讨论的其他页面类型不同，<strong>VA页面不与任何enclave关联</strong>。这意味着可以通过<code>EREMOVE</code>释放它们，而不受任何限制。但是，将一个槽正在使用的VA页面释放时会丢弃这些槽中的nonces，这将导致丢失将相应的被交换的页面加载回EPC的能力。因此，一个正确的操作系统实现不太可能在一个非空闲槽的VA调用<code>EREMOVE</code>。</p>
<p>根据SDM中<code>EPA</code>和<code>EWB</code>的伪代码，<strong>SGX使用零值来表示VA中的空槽</strong>，这意味着所有生成的nonces必须是非零的。这还意味着<code>EPA</code>通过将底层EPC页面归零来初始化VA。然而，由于软件不能访问VA的内容，无论是使用一个特殊值，或值本身都不是体系结构的。</p>
<h3 id="5-5-3-Enclave-IDs">5.5.3 Enclave IDs</h3>
<p><code>EWB</code>和<code>ELDU</code> / <code>ELDB</code>指令使用一个<strong><em>enclave ID</em> (EID)来标识拥有被交换页面的enclave</strong>。EID与EPCM条目中的ENCLAVESECS(<a href="#5.1.2-Enclave%E9%A1%B5%E9%9D%A2%E7%BC%93%E5%AD%98%E6%98%A0%E5%B0%84%E8%A1%A8-(EPCM)">§5.1.2</a>)字段具有相同的用途，后者也用于标识拥有EPC页面的enclave。本节通过比较两个值及其用法，说明使用两个值表示相同概念的必要性。</p>
<p>SDM声明EPCM条目中的ENCLAVESECS字段用于标识拥有相关EPC页面的enclave的SECS，但没有描述其格式。理论上，ENCLAVESECS字段可以在SGX实现之间更改，因为SGX指令从不向软件暴露其值。</p>
<p>然而，我们稍后将讨论，ENCLAVESECS最可信的表示是其字段中的enclave物理地址。因此，如果从EPC中交换出enclave的SECS并在另一个位置加载回该enclave，则与给定enclave关联的ENCLAVESECS值将发生更改。由此可见，<strong>ENCLAVESECS值仅适用于标识某个enclave，而其SECS仍在EPC中</strong>。</p>
<p>根据SDM,  <strong>EID字段是存储在enclave的SECS中的64位字段</strong>。SDM中的<code>ECREATE</code>伪代码显示，在分配SECS时，通过原子递增全局计数器生成enclave的ID。假设计数器不会发生滚动，此过程将确保在电源循环期间创建的每个enclave都具有唯一的EID。</p>
<p>尽管SDM没有明确保证这一点，但<strong>enclave SECS中的EID字段似乎不会被任何指令修改</strong>。这使得EID的值适合在整个enclave的生命周期内标识它，甚至在从EPC中将其SECS页面驱逐出去时也是如此。</p>
<h3 id="5-5-4-EPC页面交换">5.5.4 EPC页面交换</h3>
<p>系统软件使用<code>EWB</code>指令交换EPC页面，<code>EWB</code>指令在稍后通过<code>ELDU</code>指令生成恢复交换页面所需的所有数据，如下图所示。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128202614082.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128202614082.png" srcset="" alt="image-20201128202614082"></p>
<p><strong><code>EWB</code>的输出包括被交换的EPC页面内容的加密版本、与该页面对应的EPCM条目中的字段子集、<a href="#5.5.2-%E7%89%88%E6%9C%AC%E6%95%B0%E7%BB%84-(VA)">§5.5.2</a>中讨论的nonce和消息身份验证代码(MAC，§3.1.3)标记</strong>。除了nonce之外，<code>EWB</code>将其输出写入PRM区域之外的DRAM中，因此系统软件可以选择进一步将其交换出磁盘。</p>
<p>EPC页面内容被加密，以保护enclave数据的机密性，同时页面存储在PRM范围之外的不受信任的DRAM中。在不使用加密的情况下，系统软件可以通过将EPC页面从EPC中交换出来从而了解该页面的内容。</p>
<p><strong>页面元数据存储在页面信息(PAGEINFO)结构中</strong>，如下图所示。这个结构类似于<a href="#5.3.2-%E5%8A%A0%E8%BD%BD">§5.3.2</a>中描述的PAGEINFO结构，<strong>除了SECINFO字段被一个PCMD字段所取代</strong>，它包含一个页面加密元数据(PCMD)结构的虚拟地址。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128202629432.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128202629432.png" srcset="" alt="image-20201128202629432"></p>
<p>PAGEINFO结构中的LINADDR字段用于存储EPCM条目中的地址字段，该字段指示用于访问页面的虚拟地址。<strong>PCMD结构嵌入了<a href="#5.3.2-%E5%8A%A0%E8%BD%BD">§5.3.2</a>中描述的安全信息(SECINFO)，用于在EPCM条目中存储页面类型(PT)和访问权限标志(R, W, X)</strong>。PCMD结构还存储了enclave的ID (EID，<a href="#5.5.3-Enclave-IDs">§5.5.3</a>)。这些字段稍后由<code>ELDU</code>或<code>ELDB</code>用于填充重新加载的EPC页面的EPCM条目。</p>
<p>上面描述的元数据是未加密存储的，因此操作系统可以选择按原样使用内部信息进行自己的记帐。这对安全性没有负面影响，因为<strong>元数据不是机密的</strong>。实际上，除了enclave ID之外，所有元数据字段都是在调用<code>ECREATE</code>时由系统软件指定的。enclave ID仅用于标识EPC页面所属的enclave，而且系统软件也已经拥有该信息。</p>
<p>除了上面描述的元数据之外，PCMD结构还存储由<code>EWB</code>生成的MAC标签。<strong>MAC标记涵盖EPC页面内容的真实性、元数据和nonce</strong>。MAC标记由<code>ELDU</code>和<code>ELDB</code>检查，只有在MAC验证确认了页面数据、元数据和nonce的真实性时，它们才会将一个被交换的页面加载回EPC。这个安全检查可以防止在§3.7.3中描述的页面交换攻击。</p>
<p>与<code>EREMOVE</code>类似，<strong>如果没有其他EPCM条目的ENCLAVESECS字段引用该SECS，则<code>EWB</code>只会交换包含enclave SECS的EPC页面</strong>。同时，作为一种优化，SGX实现在交换SECS时不执行与<code>ETRACK</code>相关的检查。这是安全的，因为只有在EPC没有属于SECS的页面时，才会交换SECS，这意味着EPC中没有属于enclave的任何TCS，因此没有处理器可以执行enclave代码。</p>
<p>与任何其他EPC页面一样，可以清除持有版本数组的页面。VA页面永远不能被软件访问，所以他们不能有任何TLB条目指向他们。因此，<code>EWB</code>在不执行任何<code>ETRACK</code>相关检查的情况下交换VA页面。</p>
<p><code>EWB</code>的数据流(如下图所示)有一个方面可能会让操作系统开发人员感到困惑。该指令从寄存器(RBX)中读取将要被交换出的EPC页面的虚拟地址，并将其写入提供给它的PAGEINFO结构的LINADDR字段。可以通过在LINADDR字段中提供EPC页面的地址来删除单独的输入(RBX)。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201129122411533.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201129122411533.png" srcset="" alt="image-20201129122411533"></p>
<h3 id="5-5-5-将交换的页面加载回EPC">5.5.5 将交换的页面加载回EPC</h3>
<p>在交换出属于enclave的EPC页面之后，<strong>任何从enclave代码访问该页面的尝试都将导致页面错误(#PF，§2.8.2)</strong>。#PF将导致逻辑处理器通过AEX退出enclave模式(<a href="#5.4.3-%E5%BC%82%E6%AD%A5Enclave%E9%80%80%E5%87%BA">§5.4.3</a>)，然后调用OS内核的页面错误处理程序。</p>
<p>页面错误从AEX流程接受特殊处理。当离开enclave时，AEX逻辑专门检查触发AEX的硬件异常是否为#PF。如果是这样，AEX实现将清除CR2寄存器中最不重要的12位，该寄存器存储虚拟地址，其转换导致了页面错误。</p>
<p>通常，操作系统内核的页面处理程序需要能够从CR2中提取虚拟页码(VPN，§2.5.1)，这样它就知道需要将哪些内存页加载回DRAM。操作系统内核还可以使用12个最不重要的地址位，这不是VPN的一部分，从而更好地预测应用程序软件的内存访问模式。然而，与组成VPN的位不同，最下面的12位对于故障处理程序执行其工作不是绝对必要的。因此，SGX的AEX实现清除这12位，以限制页面错误处理程序得到的信息量。</p>
<p><strong>当OS页面错误处理程序检查CR2寄存器中的地址并确定错误地址在EPC中时，通常希望使用<code>ELDU</code>或<code>ELDB</code>指令将被交换的页面加载回EPC中</strong>。如果<code>EWB</code>的输出已经从DRAM中被交换到较慢的存储介质中，操作系统内核将不得不在调用<code>ELDU</code>  / <code>ELDB</code>之前将输出读回DRAM中。</p>
<p><code>ELDU</code>和<code>ELDB</code>验证由<code>EWB</code>产生的MAC标签，如<a href="#5.5.4-EPC%E9%A1%B5%E9%9D%A2%E4%BA%A4%E6%8D%A2">§5.5.4</a>所述。这可以防止操作系统内核执行§3.7.3中描述的基于页面交换的主动地址转换攻击。</p>
<h3 id="5-5-6-交换树">5.5.6 交换树</h3>
<p>SGX允许从EPC中交换VA页面，就像enclave页面一样。当从EPC中交换一个VA页面时，处理器将无法访问由VA槽存储的所有nonces。因此，在操作系统将VA页面加载回EPC之前，<code>ELDB</code>无法恢复与这些nonces关联的被交换的页面。</p>
<p>换句话说，<strong>一个被交换的页面依赖于存储其nonce的VA页面，并且在VA页面也被重新加载之前不能被加载回EPC</strong>。由此关系创建的依赖图是一个交换树的森林。如下图所示，一个交换树将EPC页面作为叶子，将VA页面作为内部节点。页面的父页面是保存其nonce的VA页面。由于<code>EWB</code>总是在一个页面中输出一个nonce，所以每个交换树的根节点在EPC中总是一个页面。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128202740316.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128202740316.png" srcset="" alt="image-20201128202740316"></p>
<p>一个简单的归纳表明，<strong>当OS希望将一个交换的enclave页面加载回EPC时，它需要在从交换树的根到与该enclave页面对应的叶的路径上加载所有VA页面</strong>。因此，在EPC中满足页面错误所需的页面加载数量取决于包含该页面的交换树的形状。</p>
<p>SGX让OS完全掌控交换树的形状。这对安全性没有负面影响，因为树的形状只影响交换方案的性能，而不影响其正确性。</p>
<h2 id="5-6-SGX-Enclave度量">5.6 SGX Enclave度量</h2>
<p>SGX实现了一个软件认证方案，该方案遵循了§3.3中概述的一般原则。对于本节而言，最相关的原则是远程方根据其度量对enclave进行身份验证，该度量用于识别正在enclave内部执行的软件。远程方将可信硬件报告的enclave度量与预期的度量进行比较，只有在两个值匹配时才进行。</p>
<p><a href="#5.3-SGX-Enclave%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F">§5.3</a>解释了SGX enclave是使用<code>ECREATE</code>(<a href="#5.3.1-%E5%88%9B%E5%BB%BA">§5.3.1</a>)、<code>EADD</code>(<a href="#5.3.2-%E5%8A%A0%E8%BD%BD">§5.3.2</a>)和<code>EEXTEND</code>指令构建的。在通过<code>EINIT</code>(<a href="#5.3.3-%E5%88%9D%E5%A7%8B%E5%8C%96">§5.3.3</a>)初始化enclave之后，上面提到的指令就不能再使用了。SGX度量的方案遵循§3.3.2中概述的原则，SGX enclave的度量是通过计算安全散列(§3.1.3)输入<code>ECREATE</code>,  <code>EADD</code>和<code>EEXTEND</code>。<code>EINIT</code>结束了表示enclave度量值的散列。</p>
<p>除了enclave的内容之外，enclave作者还需要指定应该使用的指令序列，以便创建一个enclave，该enclave的度量将与软件认证过程中远程方使用的期望值相匹配。.so和.dll动态加载库文件格式是SGX打算使用的enclave传递方法，它们已经包含了加载算法的非正式规范。我们期望非正式的加载规范作为规范的起点，这些规范规定了应该用于从.so和.dll文件创建enclave的SGX指令的确切序列。</p>
<p>如§3.3.2所述，<strong>enclave的度量是使用安全的哈希算法计算的，因此系统软件只能按照enclave作者指定的指令顺序构建与预期度量匹配的enclave</strong>。</p>
<p><strong>SGX设计使用256位SHA-2安全哈希函数来计算其度量值</strong>。SHA-2是一个块哈希函数(§3.1.3)，它对64字节的块进行操作，使用32字节的内部状态，并产生32字节的输出。<strong>每个enclave的度量都存储在enclave SECS的MRENCLAVE字段中</strong>。32字节字段存储256位SHA-2安全哈希函数的内部状态和最终输出。</p>
<h3 id="5-6-1-ECREATE度量">5.6.1 ECREATE度量</h3>
<p>在<a href="#5.3.1-%E5%88%9B%E5%BB%BA">§5.3.1</a>中概述的<code>ECREATE</code>指令，首先使用256位SHA-2初始化算法在新创建的SECS中初始化MRENCLAVE字段，然后使用下表中描述的64字节块扩展哈希。</p>
<img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128202858886.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128202858886.png" srcset="" alt="image-20201128202858886" style="zoom:80%;" />
<p>enclave的度量不包括BASEADDR字段。此处省略是有意的，因为它<strong>允许系统软件在满足ELRANGE限制的主进程内的任何虚拟地址加载enclave，而不会改变enclave度量值</strong>。此特性可以与生成位置无关的enclave代码的编译器结合使用，以获得可重定位的enclave。</p>
<p>enclave的度量包括SSAFRAMESIZE字段，它<strong>保证AEX创建、<code>EENTER</code>(<a href="#5.4.1-%E5%90%8C%E6%AD%A5Enclave%E8%BF%9B%E5%85%A5">§5.4.1</a>)和<code>ERESUME</code>(<a href="#5.4.4-%E4%BB%8E%E5%BC%82%E6%AD%A5%E9%80%80%E5%87%BA%E6%81%A2%E5%A4%8D">§5.4.4</a>)使用的SSAs(<a href="#5.2.5-%E7%8A%B6%E6%80%81%E4%BF%9D%E5%AD%98%E5%8C%BA%E5%9F%9F-(SSA)">§5.2.5</a>)具有enclave作者所期望的大小</strong>。如果将此字段排除在enclave的度量之外，会允许恶意的enclave加载程序通过指定比enclave作者预期的更大的SSAFRAMESIZE来试图攻击enclave的安全检查，这可能导致AEX编写的SSA内容覆盖enclave的代码或数据。</p>
<h3 id="5-6-2-度量Enclave属性">5.6.2 度量Enclave属性</h3>
<p><strong>enclave的度量不包括在SECS中的ATTRIBUTES字段中指定的enclave属性</strong>(<a href="#5.2.2-SGX-Enclave%E5%B1%9E%E6%80%A7">§5.2.2</a>)。相反，它直接包含在认证签名所涵盖的信息中，这将在<a href="#5.8.1-%E6%9C%AC%E5%9C%B0%E8%AE%A4%E8%AF%81">§5.8.1</a>中讨论。</p>
<p><strong>SGX软件认证需要涵盖enclave属性</strong>。例如，如果XFRM(<a href="#5.2.2-SGX-Enclave%E5%B1%9E%E6%80%A7">§5.2.2</a> <a href="#5.2.5-%E7%8A%B6%E6%80%81%E4%BF%9D%E5%AD%98%E5%8C%BA%E5%9F%9F-(SSA)">§5.2.5</a>)不被覆盖，一个恶意的enclave装载机可以通过设置XFRM值试图颠覆一个enclave的安全检查，让体系结构扩展指令使用的enclave的语义变化，但仍然是产生符合SSAFRAMESIZE的XSAVE输出。</p>
<p>应用到ATTRIBUTES  SECS字段的特殊处理从安全角度来看似乎是有问题的，因为它给软件认证验证器增加了额外的复杂性，从而转化为可利用的漏洞的更多机会。这个决定也增加了SGX软件认证设计的复杂性，在<a href="#5.8-SGX%E8%BD%AF%E4%BB%B6%E8%AE%A4%E8%AF%81">§5.8</a>中有描述。</p>
<p>尽管存在上述问题，SGX设计决定走这条路的最可能的原因是希望能够使用单一度量来表示一个能够利用一些架构扩展的enclave，但也可以在没有它们的情况下执行其任务。</p>
<p>例如，考虑使用OpenCV这样的库执行图像处理的enclave，它具有针对SSE和VX优化的例程，但还包括针对不具备这些特性的处理器的通用回退。enclave的作者可能希望允许enclave加载器将位1(SSE)和位2(VX)设置为真或假。如果ATTRIBUTES(以及XFRM)是enclave度量的一部分，则enclave作者必须指定enclave有4个有效度量。通常，允许n个架构扩展独立使用将导致2<sup>n</sup>个无效度量。</p>
<h3 id="5-6-3-度量EADD">5.6.3 度量EADD</h3>
<p><a href="#5.3.2-%E5%8A%A0%E8%BD%BD">§5.3.2</a>中描述的<code>EADD</code>指令用下表中所示的64字节块扩展了MRENCLAVE中的SHA-2哈希。</p>
<img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128202953982.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128202953982.png" srcset="" alt="image-20201128202953982" style="zoom:80%;" />
<p><strong>度量中包含的地址是<code>EADD</code>页面预期映射到enclave的虚拟地址空间中的地址</strong>。这可以确保系统软件根据enclave作者的规范设置enclave的虚拟内存布局。如果恶意的enclave加载程序试图错误地设置enclave的布局，可能是为了挂载一个活动的地址转换攻击(§3.7.2)，所加载的enclave的度量结果将与enclave作者期望的度量结果不同。</p>
<p>新创建的页面的虚拟地址相对于enclave的ELRANGE的起点进行度量。换句话说，度量中包含的值是LINADDR -  BASEADDR。这使得<strong>enclave的度量对BASEADDR的变化保持不变</strong>，这对于可重定位的enclave是理想的。度量相对地址仍然保留了关于ELRANGE内内存布局的所有信息，因此没有负面的安全影响。</p>
<p><strong><code>EADD</code>同时也度量提供给<code>EADD</code>的SECINFO(<a href="#5.3.2-%E5%8A%A0%E8%BD%BD">§5.3.2</a>)结构的前48字节</strong>，其中包含用于初始化页面的EPCM条目的页面类型(PT)和访问权限(R,  W, X)字段值。同样，在度量中包含这些值可以保证加载enclave的系统软件构建的内存布局与enclave作者的规范相匹配。</p>
<p>上面提到的EPCM字段值在SECINFO结构中只占不到一个字节，其余的字节被保留，并被初始化为零。这为SGX未来的功能留下了大量的扩展空间。</p>
<p>上表中最值得注意的省路是用于初始化新创建的EPC页面的数据。因此，<code>EADD</code>提供的度量数据保证了enclave的内存布局将具有在所需虚拟地址上分配的有指定访问权限的页面。但是，度量不包括在这些页面中加载的代码或数据。</p>
<p>例如，<code>EADD</code>的度量数据保证enclave的内存布局由三个可执行页面和五个可写数据页面组成，但它不能保证任何代码页面包含enclave作者提供的代码。</p>
<h3 id="5-6-4-度量EEXTEND">5.6.4 度量EEXTEND</h3>
<p><code>EEXTEND</code>指令的存在仅仅是为了度量加载在enclave的EPC页面中的数据。该指令读入一个虚拟地址，并使用下表中的5个64字节块扩展enclave的度量散列，这有效地保证了enclave内存中256字节数据块的内容。</p>
<img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128203446836.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128203446836.png" srcset="" alt="image-20201128203446836" style="zoom:80%;" />
<p>在检查<code>EEXTEND</code>的细节之前，我们注意到，SGX的安全保证只有在对enclave的关键页面的内容进行测量时才有效。例如，<code>EENTER</code>(<a href="#5.4.1-%E5%90%8C%E6%AD%A5Enclave%E8%BF%9B%E5%85%A5">§5.4.1</a>)只有在测量了所有线程控制结构(TCS，<a href="#5.2.4-%E7%BA%BF%E7%A8%8B%E6%8E%A7%E5%88%B6%E7%BB%93%E6%9E%84-(TCS)">§5.2.4</a>)页面的内容后，才保证在enclave的代码中执行受控跳转。否则，恶意的enclave加载程序可以在构建enclave时更改TCS中的OENTRY字段(§5.2.4，<a href="#5.4.1-%E5%90%8C%E6%AD%A5Enclave%E8%BF%9B%E5%85%A5">§5.4.1</a>)，然后恶意的操作系统可以使用TCS在enclave代码中执行任意跳转。同样的道理，所有enclave的代码都应该用<code>EEXTEND</code>来度量。任何无法测量的代码片段都可以被恶意的enclave加载程序替换。</p>
<p>考虑到这些缺陷，令人惊讶的是SGX设计选择将由<code>EADD</code>完成的虚拟地址空间布局度量与由<code>EEXTEND</code>完成的内存内容度量分离。</p>
<p>乍一看，这种解耦似乎只有一个好处，即能够在构建enclave时将未测量的用户输入加载到该enclave中。但是，这种好处只会转化为很小的性能改进，因为可以将enclave设计为在初始化后从不受信任的DRAM复制用户输入。与此同时，由于没有通过<code>EEXTEND</code>调用度量所有重要数据，这种分离打开了依赖于一个没有提供有意义的安全保证的enclave的可能性。</p>
<p>然而，<code>EADD</code>  / <code>EEXTEND</code>分离背后的真正原因是由SDM中的<code>EINIT</code>伪代码所暗示的，它表明该指令在执行计算密集型RSA签名检查时打开了一个中断窗口(§2.12)。如果在检查期间发生中断，<code>EINIT</code>会失败并出现错误代码，然后中断。对于处理器指令来说，这种非常不寻常的方法表明，SGX实现受到了其指令被允许添加到中断处理进程中的延迟时间的限制。</p>
<p>考虑到上面的问题，可以合理地推断引入了<code>EEXTEND</code>，因为使用256位SHA-2度量整个页面非常耗时，而且在<code>EADD</code>中这样做会导致指令超出SGX的延迟预算。需要达到某个延迟目标，这是对似乎任意的256字节块大小的合理解释。</p>
<p>如果使用构建当前动态加载模块(似乎是SGX设计的目标工作流)的相同工具编写enclave，那么<code>EADD</code> /  <code>EEXTEND</code>分离将不会导致安全问题。在此工作流中，构建enclave的工具可以轻松识别需要度量的enclave数据。</p>
<p>从安全性的角度来看，让<code>EEXTEND</code>提供给散列函数的消息块除了包含数据内容外，还包含256字节块的地址是正确的，也是有意义的。如果地址不包括在内，恶意的enclave加载器就会装载§3.7.2中描述的内存映射攻击。</p>
<p>更具体地说，恶意加载程序将在用于泄漏的虚拟地址处<code>EADD</code> <code>errorOut</code>页面内容，在用于泄漏的虚拟地址处<code>EADD disclose</code>页面内容，然后以错误的顺序<code>EEXTEND</code>页面。如果<code>EEXTEND</code>不包含被测量的数据块的地址，那么上面的步骤将产生与正确构造的enclave相同的测量结果。</p>
<p><code>EEXTEND</code>值得分析的最后一个方面是它对enclave迁移的支持。与<code>EADD</code>类似，<code>EEXTEND</code>测量的虚拟地址是相对于enclave的BASEADDR的。此外，唯一的SGX结构的内容将被<code>EEXTEND</code>测量是TCS。SGX的设计已经仔细地为所有代表enclave地址的TCS字段使用了相对地址，这是OENTRY,  OFSBASGX和OGSBASGX。</p>
<h3 id="5-6-5-度量EINIT">5.6.5 度量EINIT</h3>
<p><code>EINIT</code>指令(<a href="#5.3.3-%E5%88%9D%E5%A7%8B%E5%8C%96">§5.3.3</a>)结束了enclave的构建过程。在enclave上成功调用<code>EINIT</code>之后，enclave的内容是“密封的”，这意味着系统软件不能使用<code>EADD</code>指令将代码和数据加载到enclave中，也不能使用<code>EEXTEND</code>指令更新enclave的度量。</p>
<p><code>EINIT</code>在enclave的SECS的MRENCLAVE字段上使用SHA-2算法(§3.1.3)。在<code>EINIT</code>之后，字段不再存储SHA-2算法的中间状态，而是存储安全哈希函数的最终输出。该值在<code>EINIT</code>完成后保持不变，并包含在SGX软件认证过程生成的认证签名中。</p>
<h2 id="5-7-SGX-Enclave版本支持">5.7 SGX Enclave版本支持</h2>
<p>由可信平台模块(§4.4)引入的软件认证模型(§3.3)依赖于度量，它本质上是一个内容散列，用于识别容器内的软件。使用内容哈希进行标识的缺点是，存放同一软件的不同版本的容器的标识之间没有关系。</p>
<p>在实践中，基于安全容器的系统在初始软件认证过程中不访问远程方来处理软件更新是非常理想的。这需要有能力在拥有旧版本软件的容器和拥有更新版本的容器之间迁移秘密。这一需求转化为对能够识别同一软件的两个版本之间关系的独立身份系统的需求。</p>
<p>SGX支持在表示同一软件的不同版本的enclave之间迁移秘密，如下图所示。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128203650801.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128203650801.png" srcset="" alt="image-20201128203650801"></p>
<p>秘密迁移特性依赖于一个一级证书层次结构(§3.2.1)，其中每个enclave作者都是一个证书颁发机构，每个enclave从作者那里收到一个证书。这些证书必须格式化为签名结构(SIGSTRUCT)，这在<a href="#5.7.1-Enclave%E8%AF%81%E4%B9%A6">§5.7.1</a>中有描述。这些证书中的信息是<a href="#5.7.2-%E5%9F%BA%E4%BA%8E%E8%AF%81%E4%B9%A6%E7%9A%84Enclave%E8%BA%AB%E4%BB%BD">§5.7.2</a>中介绍的enclave身份方案的基础，它可以识别同一软件的不同版本之间的关系。</p>
<p><code>EINIT</code>指令(<a href="#5.3.3-%E5%88%9D%E5%A7%8B%E5%8C%96">§5.3.3</a>)检查目标enclave的证书，并使用其中的信息填充SECS(<a href="#5.1.3-SGX-Enclave%E6%8E%A7%E5%88%B6%E7%BB%93%E6%9E%84-(SECS)">§5.1.3</a>)字段，这些字段描述了enclave基于证书的标识。这个过程在<a href="#5.7.4-%E5%BB%BA%E7%AB%8BEnclave%E8%BA%AB%E4%BB%BD">§5.7.4</a>中进行了总结。</p>
<p>最后，实际的秘密迁移过程是基于<code>EGETKEY</code>指令实现的密钥派生服务，在<a href="#5.7.5-Enclave%E5%AF%86%E9%92%A5%E5%88%86%E5%8F%91">§5.7.5</a>中进行了描述。发送enclave使用<code>EGETKEY</code>指令根据其身份获得一个对称密钥(§3.1.1)，用该密钥加密其秘密，并将加密的秘密交给不受信任的系统软件。接收enclave将发送enclave的身份传递给<code>EGETKEY</code>，获得与上面相同的对称密钥，并使用该密钥解密从系统软件接收到的秘密。</p>
<p>从<code>EGETKEY</code>获得的对称密钥可以与加密原语一起使用，这些原语可以在不受信任的系统软件迁移到另一个enclave时保护enclave秘密的机密性(§3.1.2)和完整性(§3.1.3)。但是，仅凭对称密钥无法提供新鲜度保证(§3.1)，因此秘密迁移容易受到重放攻击。当被迁移的秘密是不可变的，例如当秘密是通过软件认证获得的加密密钥时，这是可以接受的。</p>
<h3 id="5-7-1-Enclave证书">5.7.1 Enclave证书</h3>
<p>SGX的设计要求每个enclave都有其作者颁发的证书。这一要求是由<code>EINIT</code>强制执行的(<a href="#5.3.3-%E5%88%9D%E5%A7%8B%E5%8C%96">§5.3.3</a>)，它拒绝在没有有效证书的enclave上操作。</p>
<p>SGX实现使用格式化为签名结构(SIGSTRUCT)的证书，这些证书将由enclave构建工具链生成，如下图所示。</p>
<p><img src="D:%5CSource%5Cnotes%5Ctranslation-sgx-programming-model%5Cimage-20201128203825740.png" class="lazyload" data-srcset="D:%5CSource%5Cnotes%5Ctranslation-sgx-programming-model%5Cimage-20201128203825740.png" srcset="" alt="image-20201128203825740"></p>
<p>SIGSTRUCT证书由元数据字段和保证元数据真实性的（其中最有趣的字段在下表中显示）。字段的语义将在下面的部分中揭示。</p>
<img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128203806483.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128203806483.png" srcset="" alt="image-20201128203806483" style="zoom:80%;" />
<p>RSA签名组成格式如下表所示：</p>
<img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128203943298.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128203943298.png" srcset="" alt="image-20201128203943298" style="zoom:80%;" />
<p>enclave证书必须由RSA签名(§3.1.3)签名，该签名遵循RFC 3447中描述的方法，使用256位SHA-2]作为哈希函数来减少输入大小，以及PKCS #1 v1.5中描述的填充方法。</p>
<h3 id="5-7-2-基于证书的Enclave身份">5.7.2 基于证书的Enclave身份</h3>
<p>enclave的身份由其证书中的三个字段决定(<a href="#5.7.1-Enclave%E8%AF%81%E4%B9%A6">§5.7.1</a>)：用于签署证书的RSA密钥的模数(模数)、enclave的产品ID(ISVPRODID)和安全版本号(ISVSVN)。</p>
<p>用于颁发证书的公共RSA密钥标识enclave的作者。用于颁发enclave证书的所有RSA密钥必须将公开指数设置为3，因此它们仅通过模量进行区分。SGX不使用密钥的整个模量，而是模量的256位SHA-2哈希。这称为签名者度量(MRSIGNER)，与标识enclave内容的SHA-2散列的enclave度量(MRENCLAVE)名称量级相同。</p>
<p>SGX实现依赖于一个硬编码的MRSIGNER值来识别由Intel颁发的证书。拥有英特尔颁发的证书的enclave可以获得额外的特权，这在<a href="#5.8-SGX%E8%BD%AF%E4%BB%B6%E8%AE%A4%E8%AF%81">§5.8</a>中有讨论。</p>
<p>enclave作者可以使用相同的RSA密钥为表示不同软件模块的enclave颁发证书。每个模块由一个唯一的产品ID(ISVPRODID)值标识。相反，假定证书具有相同ISVPRODID并由相同RSA密钥(因此具有相同的MRENCLAVE)颁发的所有enclave代表同一软件模块的不同版本。通常假定证书由不同密钥签名的enclave包含不同的软件模块。</p>
<p>表示模块的不同版本的enclave可以具有不同的安全版本号(SVN)。SGX的设计不允许秘密从具有较高SVN的enclave迁移到具有较低SVN的enclave。此限制旨在帮助分发安全补丁，如下所示。</p>
<p>如果在enclave中发现安全漏洞，作者可以发布具有更高SVN的固定版本。随着用户升级，SGX将帮助机密从enclave的脆弱版本转移到固定版本。一旦用户的秘密迁移完成，SGX中的SVN限制将基于构建易受攻击的enclave版本并使用它读取迁移后的秘密来转移任何攻击。</p>
<p>增加功能的软件升级不应该伴随着SVN的增加，因为SGX允许秘密在具有匹配SVN值的enclave之间自由迁移。如上所述，只有在发现安全漏洞时，软件模块的SVN才应该增加。SIGSTRUCT只给ISVSVN字段分配2个字节，转换成65,536个可能的SVN值。如果一个大型团队(错误地)设置了一个连续构建系统，为它生成的每个软件构建分配一个新的SVN，并且每个代码更改触发一个构建，那么这个空间就会耗尽。</p>
<h3 id="5-7-3-CPU安全版本号">5.7.3 CPU安全版本号</h3>
<p>SGX实现本身有一个安全版本号(CPUSVN)，除了enclave的身份信息之外，它还用于<code>EGETKEY</code>实现的密钥派生过程中。CPUSVN是一个128位的值，根据SDM，它反映了处理器的微码更新版本。</p>
<p>SDM没有描述CPUSVN的结构，但是它指出使用整型比较来比较CPUSVN的值是没有意义的，并且只有一些CPUSVN值是有效的。此外，CPUSVNs承认的排序关系与enclave SVNs之间的排序关系具有相同的语义。特别地，一个SGX实现将考虑所有具有较低SVN的SGX实现由于安全漏洞而受到损害，并且不会信任它们。</p>
<p>SGX的一项专利披露，CPUSVN是小整数的串联，这些小整数表示构成SGX实现的各种组件的svn。这种结构与SDM中所做的所有声明一致。</p>
<h3 id="5-7-4-建立Enclave身份">5.7.4 建立Enclave身份</h3>
<p>当EINIT(<a href="#5.3.3-%E5%88%9D%E5%A7%8B%E5%8C%96">§5.3.3</a>)指令准备执行代码的enclave时，它还会设置SECS(<a href="#5.1.3-SGX-Enclave%E6%8E%A7%E5%88%B6%E7%BB%93%E6%9E%84-(SECS)">§5.1.3</a>)字段，这些字段组成enclave的基于证书的标识，如下图所示。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128204429025.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128204429025.png" srcset="" alt="image-20201128204429025"></p>
<p><code>EINIT</code>需要颁发给enclave的SIGSTRUCT证书的虚拟地址，并使用证书中的信息初始化enclave的SECS中的基于证书的标识信息。在使用证书中的信息之前，<code>EINIT</code>首先验证其RSA签名。SIGSTRUCT字段Q1和Q2，以及RSA指数3，简化了验证算法，这在§6.5中讨论。</p>
<p>如果发现SIGSTRUCT证书被正确签名，<code>EINIT</code>将按照下面几段中讨论的步骤确保将证书颁发给正在初始化的enclave。检查完成后，<code>EINIT</code>计算MRSIGNER,  SIGSTRUCT中模数字段的256位SHA-2哈希，并将其写入enclave的SECS中。<code>EINIT</code>还将ISVPRODID和ISVSVN字段从SIGSTRUCT复制到enclave的SECS中。正如在<a href="#5.7.2-%E5%9F%BA%E4%BA%8E%E8%AF%81%E4%B9%A6%E7%9A%84Enclave%E8%BA%AB%E4%BB%BD">§5.7.2</a>中所解释的，这些字段构成了enclave的基于证书的标识。</p>
<p>在验证了SIGSTRUCT中的RSA签名之后，<code>EINIT</code>将签名的填充复制到enclave的SECS中的填充字段中。PKCS #1 v1.5填充方案不包含随机性，因此填充对于所有enclave应该具有相同的值。</p>
<p><code>EINIT</code>执行一些检查，以确保正在进行初始化的enclave确实是由提供的SIGSTRUCT证书授权的。最明显的检查包括确保SIGSTRUCT中的MRENCLAVE值等于enclave的度量，后者存储在enclave的SECS中的MRENCLAVE字段中。</p>
<p>但是，MRENCLAVE没有涵盖enclave的属性，这些属性存储在SECS的ATTRIBUTES字段中。正如在<a href="#5.6.2-%E5%BA%A6%E9%87%8FEnclave%E5%B1%9E%E6%80%A7">§5.6.2</a>中所讨论的，从MRENCLAVE中省略一个ATTRIBUTES有助于编写具有优化实现的enclave，这些实现可以在存在时使用架构扩展，也可以在没有扩展的情况下在CPU上工作的回退实现。当使用XFRM(<a href="#5.2.2-SGX-Enclave%E5%B1%9E%E6%80%A7">§5.2.2</a> <a href="#5.2.5-%E7%8A%B6%E6%80%81%E4%BF%9D%E5%AD%98%E5%8C%BA%E5%9F%9F-(SSA)">§5.2.5</a>)属性中的各种值构建时，这样的enclave可以正确执行。与此同时，允许系统软件在ATTRIBUTES字段中使用任意值，将危及SGX的安全保证。</p>
<p>当enclave使用软件认证(§3.3)访问机密时，用于构建该机密的附加值包含在SGX认证签名(<a href="#5.8-SGX%E8%BD%AF%E4%BB%B6%E8%AE%A4%E8%AF%81">§5.8</a>)中。这使认证过程中的远程方有机会拒绝使用不需要的ATTRIBUTES值构建的enclave。但是，当使用基于证书的身份验证的迁移过程获得机密时，没有远程方可以检查enclave的属性。</p>
<p>SGX设计通过让enclave作者在为enclave颁发的SIGSTRUCT证书的ATTRIBUTES和ATTRIBUTEMASK字段中为enclave传递一组可接受的属性值来解决这个问题。如果在SECS中的ATTRIBUTES字段和SIGSTRUCT中的ATTRIBUTESMASK字段之间的位不等于SIGSTRUCT中的ATTRIBUTES字段，<code>EINIT</code>将拒绝使用SIGSTRUCT初始化enclave。此检查可防止具有不需要的属性的enclave在迁移过程中获取或泄漏机密。</p>
<p>任何enclave作者都可以使用SIGSTRUCT来请求enclave的ATTRIBUTES字段中的任何位为零。但是，对于由Intel签署的enclave，某些位只能设置为1。<code>EINIT</code>有一个限制ATTRIBUTES比特的掩码，在<a href="#5.8-SGX%E8%BD%AF%E4%BB%B6%E8%AE%A4%E8%AF%81">§5.8</a>中讨论过。<code>EINIT</code>实现包含一个硬编码的MRSIGNER值，该值用于标识英特尔的特权enclave，并且只允许使用与受限制掩码中的任何位匹配的ATTRIBUTES值构建特权enclave。这个检查对于SGX软件认证过程的安全性是至关重要的，这在<a href="#5.8-SGX%E8%BD%AF%E4%BB%B6%E8%AE%A4%E8%AF%81">§5.8</a>中描述。</p>
<p>最后，<code>EINIT</code>还检查SIGSTRUCT中的VENDOR字段。专用于SIGSTRUCT的一节中对VENDOR字段的SDM描述表明，该字段本质上用于区分由Intel签名的特殊enclave(使用VENDOR值0x8086)和其他所有人的enclave(使用VENDOR值0)。然而，<code>EINIT</code>伪代码似乎暗示了SGX实现只检查VENDOR是否为0或0x8086。</p>
<h3 id="5-7-5-Enclave密钥分发">5.7.5 Enclave密钥分发</h3>
<p>SGX的秘密迁移机制基于<code>EGETKEY</code>指令提供给enclave的对称密钥分发服务，如下图所示。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128204651510.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128204651510.png" srcset="" alt="image-20201128204651510"></p>
<p><code>EGETKEY</code>生成的密钥是基于当前enclave的SECS中的身份信息和存储在支持SGX的处理器内的安全硬件中的两个秘密分发的。其中一个秘密是对基本没有文档记录的一系列转换的输入，这些转换为密钥分发过程背后的加密原语生成对称密钥。另一个秘密，在SDM中称为CR_SEAL_FUSES，是在关键推导材料中使用的信息片段之一。</p>
<p>SDM没有指定密钥分发算法，但SGX专利披露密钥是使用FIPS SP 800-108中描述的方法，使用AES-CMAC]作为伪随机函数(PRF)。同样的专利声明，用于密钥分发的秘密存储在CPU的e-fuse中，这一点在ISCA 2015 SGX教程中得到证实。</p>
<p>这一附加信息意味着，使用相同密钥分发材料的所有<code>EGETKEY</code>调用将产生相同的密钥，即使是跨CPU能量周期。此外，如果不访问存储在CPU e-fuse中的密钥，对手是不可能从特定密钥派生材料中获得密钥的。SGX的关键层次结构在<a href="#5.8.2%E8%BF%9C%E7%A8%8B%E8%AE%A4%E8%AF%81">§5.8.2</a>中有进一步的描述。</p>
<p>下面的段落讨论密钥分发材料中使用的数据片段，这些数据片段由下表中所示的密钥请求(KEYREQUEST)结构选择：</p>
<img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128204721723.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128204721723.png" srcset="" alt="image-20201128204721723" style="zoom:80%;" />
<p>KEYREQUEST中的KEYNAME字段总是参与密钥生成，它指示要生成的键的类型。虽然SGX设计定义了一些密钥类型，但秘密迁移特性总是使用密封密钥。其他关键类型由SGX软件认证过程使用，将在<a href="#5.8-SGX%E8%BD%AF%E4%BB%B6%E8%AE%A4%E8%AF%81">§5.8</a>中概述。</p>
<p>KEYREQUEST中的KEYPOLICY字段有两个标志，用于指示是否将使用enclave的SECS中的MRENCLAVE和MRSIGNER字段进行密钥分发。虽然字段允许4个值，但只有两个值是有意义的，如下所述。</p>
<p>在KEYPOLICY中设置MRENCLAVE标志将分发的密钥与反映其内容的当前enclave度量绑定。其他enclave将无法获得相同的密钥。当分发的密钥用于加密enclave秘密时，这是非常有用的，因此它们可以由系统软件存储在非易失性内存中，从而在电源周期中存活。</p>
<p>如果设置了KEYPOLICY中的MRSIGNER标志，则分发的密钥将绑定到发布enclave证书的公共RSA密钥。因此，由同一作者发布的其他enclave可能能够获得相同的密钥，但要遵守下面的限制。这是唯一允许秘密迁移的KEYPOLICY值。</p>
<p>在KEYPOLICY中不设置标志没有什么意义。在这种情况下，分发的密钥没有有用的安全属性，因为它可以由与调用<code>EGETKEY</code>的enclave完全无关的其他enclave获得。相反，设置两个标志是多余的，因为仅设置MRENCLAVE就会导致分发的密钥绑定到当前enclave，这是最严格的策略。</p>
<p>KEYREQUEST结构指定了密钥分发过程中使用的enclave SVN (ISVSVN，<a href="#5.7.2-%E5%9F%BA%E4%BA%8E%E8%AF%81%E4%B9%A6%E7%9A%84Enclave%E8%BA%AB%E4%BB%BD">§5.7.2</a>)和SGX实现SVN  (CPUSVN，<a href="#5.7.3-CPU%E5%AE%89%E5%85%A8%E7%89%88%E6%9C%AC%E5%8F%B7">§5.7.3</a>)。但是，如果期望的enclave SVN大于当前enclave的SVN，或者期望的SGX实现的SVN大于当前实现的SVN,  <code>EGETKEY</code>将拒绝派生请求并生成一个错误代码。</p>
<p>SVN限制可以防止秘密从具有较高SVN的集合转移到具有较低SVN的集合，或者从具有较高SVN的SGX实现转移到具有较低SVN的实现。<a href="#5.7.2-%E5%9F%BA%E4%BA%8E%E8%AF%81%E4%B9%A6%E7%9A%84Enclave%E8%BA%AB%E4%BB%BD">§5.7.2</a>认为SVN限制可以减少enclave和SGX实施中安全漏洞的影响。</p>
<p><code>EGETKEY</code>总是使用来自当前enclave的SECS的ISVPRODID值进行密钥分发。因此，秘密永远不能在其SIGSTRUCT证书为其分配不同产品ID的enclaves之间流动。</p>
<p>类似地，密钥分发材料总是包含128位所有者轮数(OWNEREPOCH)SGX配置寄存器的值。这个寄存器是由计算机的固件设置为一个秘密生成一次，并存储在非易失性内存。在计算机更改所有权之前，旧的所有者可以从非易失性内存中清除OWNEREPOCH，从而使新所有者无法解密任何可能留在计算机上的enclave机密。</p>
<p>由于密钥分发过程的加密特性，外部观察者无法关联使用不同的OWNEREPOCH值分发的密钥。这使得软件开发人员不可能使用本节中描述的<code>EGETKEY</code>分发的密钥来跟踪处理器更改所有者。</p>
<p><code>EGETKEY</code>分发材料还在KEYID字段中包含由enclave提供的256bit值。这使得enclave可以从<code>EGETKEY</code>生成密钥集合，而不是单个密钥。SDM规定KEYID应该用随机数填充，目的是帮助防止密钥损耗。</p>
<p>最后，密钥分发材料包括enclave的SECS中的ATTRIBUTES(<a href="#5.2.2-SGX-Enclave%E5%B1%9E%E6%80%A7">§5.2.2</a>)字段的位和和KEYREQUEST结构中的ATTRIBUTESMASK字段。该掩码的作用是从密钥分发材料中删除一些ATTRIBUTES比特，从而使在具有不同属性的enclave之间迁移秘密成为可能。<a href="#5.6.2-%E5%BA%A6%E9%87%8FEnclave%E5%B1%9E%E6%80%A7">§5.6.2</a>和<a href="#5.7.4-%E5%BB%BA%E7%AB%8BEnclave%E8%BA%AB%E4%BB%BD">§5.7.4</a>解释此功能的需要及其安全含义。</p>
<p>在将掩码属性值添加到密钥生成材料之前，<code>EGETKEY</code>强制掩码位对应<code>INIT</code>和调试属性(<a href="#5.2.2-SGX-Enclave%E5%B1%9E%E6%80%A7">§5.2.2</a>)。从实用的角度来看，这意味着秘密永远不会支持调试和生产的enclave之间迁移。</p>
<p>如果没有此限制，enclave作者使用相同的RSA密钥向调试和生产enclave颁发证书将是不安全的。调试enclave没有从SGX获得完整性保证，因此攻击者有可能修改调试enclave内的代码，使其泄漏它所能访问的任何秘密。</p>
<h2 id="5-8-SGX软件认证">5.8 SGX软件认证</h2>
<p>SGX实施的软件认证方案遵循了§3.3中概述的原则。启用SGX的处理器计算加载在每个enclave中的代码和数据的度量，这类似于TPM计算的度量(§4.4)。enclave内部的软件可以启动一个进程，从而产生SGX认证签名，其中包括enclave的度量和enclave消息。</p>
<p>SGX认证签名中使用的密码原语过于复杂，无法在硬件上实现，因此签名过程是由英特尔发行的一个有特权的引用Enclave来执行的，它可以访问SGX认证密钥。这个enclave在<a href="#5.8.2-%E8%BF%9C%E7%A8%8B%E8%AE%A4%E8%AF%81">§5.8.2</a>中进行了讨论。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128204833328.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128204833328.png" srcset="" alt="image-20201128204833328"></p>
<p>图79: 建立一个SGX enclave并进行软件认证过程涉及到SGX指令<code>EINIT</code>和<code>EREPORT</code>，以及两个由英特尔编写的特殊enclave，SGX启动enclave和SGX引用enclave。</p>
<p>将签名功能推入引用Enclave，就需要在正在进行软件认证的Enclave和引用Enclave之间建立安全通信路径。SGX设计通过本地认证机制解决了这个问题，enclave可以使用该机制向同一启用SGX的CPU托管的任何其他enclave证明其身份。<a href="#5.8.1-%E6%9C%AC%E5%9C%B0%E8%AE%A4%E8%AF%81">§5.8.1</a>中描述的这个方案是通过<code>EREPORT</code>指令实现的。</p>
<p>引用enclave使用的SGX认证密钥在启用SGX的处理器离开工厂时不存在。认证密钥是稍后提供的，使用的过程包括一个由Intel发布的配置Enclave和两种特殊的<code>EGETKEY</code>(<a href="#5.7.5-Enclave%E5%AF%86%E9%92%A5%E5%88%86%E5%8F%91">§5.7.5</a>)密钥类型。这一过程的公开细节概述在<a href="#5.8.2-%E8%BF%9C%E7%A8%8B%E8%AE%A4%E8%AF%81">§5.8.2</a>中。</p>
<p>SGX启动enclave和<code>EINITTOKEN</code>结构将在<a href="#5.9-SGX%E5%90%AF%E5%8A%A8%E6%8E%A7%E5%88%B6">§5.9</a>中讨论。</p>
<h3 id="5-8-1-本地认证">5.8.1 本地认证</h3>
<p>enclave通过如图80所示的<code>EREPORT</code>指令向另一个目标enclave证明其身份。SGX指令生成一个认证报告(报告)，该报告以加密方式将enclave提供的消息与enclave的基于度量(<a href="#5.6-SGX-Enclave%E5%BA%A6%E9%87%8F">§5.6</a>)和基于证书(<a href="#5.7.2-%E5%9F%BA%E4%BA%8E%E8%AF%81%E4%B9%A6%E7%9A%84Enclave%E8%BA%AB%E4%BB%BD">§5.7.2</a>)的身份绑定。加密绑定由MAC标签(§3.1.3)完成，MAC标签使用对称密钥计算，该密钥仅在目标enclave和SGX实现之间共享。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128204914002.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128204914002.png" srcset="" alt="image-20201128204914002"></p>
<p>​												图80：EREPORT数据流</p>
<p><code>EREPORT</code>指令从enclave的SECS(<a href="#5.1.3-SGX-Enclave%E6%8E%A7%E5%88%B6%E7%BB%93%E6%9E%84-(SECS)">§5.1.3</a>)中读取当前enclave的标识信息，并使用它填充报告结构。具体来说，<code>EREPORT</code>会复制SECS字段，这些字段表示enclave的度量(MRENCLAVE)、基于证书的身份(MRSIGNER、ISVPRODID、ISVSVN)和属性(ATTRIBUTES)。认证报告还包括SGX实现的SVN(CPUSVN)和enclave提供的64字节(512位)消息。</p>
<p>接收认证报告的目标enclave可以确信报告的真实性，如图81所示。这份报告的真实性证明是它的MAC标签。验证MAC所需的密钥只能由目标enclave获得，通过要求<code>EGETKEY</code>(<a href="#5.7.5-Enclave%E5%AF%86%E9%92%A5%E5%88%86%E5%8F%91">§5.7.5</a>)分发一个报告密钥。SDM声明MAC标记是使用基于块加密的MAC (CMAC)计算的，但是没有指定底层密码。SGX文件之一声明CMAC是基于128位AES。</p>
<p><code>EGETKEY</code>返回的报告密钥来自于嵌入在处理器中的一个秘密(<a href="#5.7.5-Enclave%E5%AF%86%E9%92%A5%E5%88%86%E5%8F%91">§5.7.5</a>)，密钥材料包括目标enclave的度量。可以确保目标enclave报告中的MAC标签是由SGX产生的，原因如下。底层密钥分发的加密特性推导和MAC算法确保只有SGX所实现可以产生MAC标签，因为它是唯一的实体，可以访问处理器的秘密，这是不可能对攻击者获得报告密钥不知道处理器的秘密。SGX的设计保证了<code>EGETKEY</code>生成的密钥依赖于调用enclave的度量，因此只有目标enclave才能获得报告中用于生成MAC标签的密钥。</p>
<p>当KEYNAME设置为与报告密钥相关联的值时，<code>EREPORT</code>使用与EGETKEY相同的密钥分发过程。因此，<code>EREPORT</code>需要<em>报告目标信息</em>(TARGETINFO)结构的虚拟地址，该结构包含基于度量的标识和目标enclave的属性。</p>
<p>在分发一个报告密钥时，<code>EGETKEY</code>的行为与它在密封密钥情况下的行为略有不同，如图81所示。密钥生成材料从不包括与enclave的基于证书的身份(MRSIGNER、ISVPRODID、ISVSVN)对应的字段，并且KEYREQUEST结构中的KEYPOLICY字段将被忽略。因此，该报告只能由目标enclave加以核实。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128204934312.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128204934312.png" srcset="" alt="image-20201128204934312"></p>
<p>图81: 由<code>EREPORT</code>创建的报告结构的真实性可以而且应该由报告的目标enclave验证。目标代码使用<code>EGETKEY</code>获取嵌入在报告结构中的MAC标记使用的密钥，然后验证标记。</p>
<p>此外，SGX实现用于密钥生成的SVN  (CPUSVN)值是由当前的CPUSVN决定的，而不是从密钥请求结构中读取。因此，SGX实现升级，增加CPUSVN使所有未完成的报告无效。鉴于CPUSVN的增加与安全修复有关，<a href="#5.7.2-%E5%9F%BA%E4%BA%8E%E8%AF%81%E4%B9%A6%E7%9A%84Enclave%E8%BA%AB%E4%BB%BD">§5.7.2</a>中的论证表明，这一限制可能会减少SGX实现中漏洞的影响。</p>
<p>最后，<code>EREPORT</code>将密钥生成材料中的KEYID字段设置为SGX配置寄存器(CR_REPORT_KEYID)的内容，该寄存器在初始化SGX时以随机值初始化。KEYID值也保存在认证报告中，但是它不包含在MAC标签中。</p>
<!--

### 5.8.2 远程认证

远程认证过程包括引用Enclave和基础密钥，在Intel的一份出版物中对其进行了高层介绍。本节的内容基于SDM、一篇SGX论文和ISCA 2015 SGX tutorial。

SGX的软件认证方案，如图82所示，依赖于密钥生成设施和供应服务，两者都由英特尔运营。

![image-20201128205219513](https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128205219513.png)

图82:SGX的软件认证基于存储在处理器芯片内的e-fuse中的两个秘密，以及从英特尔供应服务接收的密钥。

在制造过程中，一个启用了SGX的处理器与英特尔的密钥生成设备进行通信，并有两个秘密被烧到e-fuse中。e-fuse是一种一次性可编程存储介质，可以经济地包含在高性能芯片上。我们将把存储在e-fuse中的秘密称为供应秘密和密封秘密。

供应秘密是输出`EGETKEY`使用的SGX主衍生密钥的过程的主要输入，图78、79、80和81中引用了这个过程。

密封秘密不会通过SDM中记录的任何架构机制暴露给软件。只有当秘密包含在`EGETKEY`实现的密钥推导过程所使用的材料中时，该秘密才能被访问([§5.7.5](#5.7.5-Enclave密钥分发))。SDM中的伪代码使用CR_SEAL_FUSES起存其名来引用密封机密。

名称“Seal Secret”和“Provisioning Secret”与英特尔的官方文件有所不同，后者使用“Seal Key”和“Provisioning  Key”来指代存储在e-fuse中的秘密和`EGETKEY`分发的密钥，这令人困惑。

SDM简要地描述了`EGETKEY`生成的密钥，但是没有官方文档明确地描述e-fuse中的秘密。下面的描述是对所有公共信息来源的唯一解释，它与SDM关于关键来源的所有声明一致。



![image-20201128205236786](https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128205236786.png)



![image-20201128205249675](https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128205249675.png)

## 5.9 SGX启动控制

### 5.9.1 Enclave属性访问控制

![image-20201128210858231](https://cdn.jsdelivr.net/gh/Schenk75/Source@master/notes/translation-sgx-programming-model/image-20201128210858231.png)

### 5.9.2 许可证

### 5.9.3 系统软件可以执行启动策略

### 5.9.4 Enclave不能损坏主机计算机

### 5.9.5 与杀毒软件交互

-->
  
  
    
    <div class='footer'>
      
      
      
      
    </div>
  
  
    


  <div class='article-meta' id="bottom">
    <div class='new-meta-box'>
      
        
          <div class="new-meta-item date">
  <a class='notlink'>
    <i class="fas fa-calendar-alt fa-fw" aria-hidden="true"></i>
    <p>发布于：2020年11月28日</p>
  </a>
</div>

        
      
        
          
  
  <div class="new-meta-item meta-tags"><a class="tag" href="/tags/sgx/" rel="nofollow"><i class="fas fa-hashtag fa-fw" aria-hidden="true"></i><p>sgx</p></a></div>


        
      
    </div>
  </div>


  
  

  
    <div class="prev-next">
      
        <a class='prev' href='/2020/11/29/paper-reading/translation-sgx-analysis/'>
          <p class='title'><i class="fas fa-chevron-left" aria-hidden="true"></i>翻译-SGX Analysis</p>
          <p class='content'>放在博客上链接不能跳转指定标题，暂时没有办法解决
6.1 SGX实现概述
SGX设计实现的一个未被记录和忽视的成就是，在英特尔处理器上实现它对芯片的硬件设计有非常低的影响。SGX对处理器执行核心...</p>
        </a>
      
      
        <a class='next' href='/2020/11/26/useful-skills/csdn-pdf/'>
          <p class='title'>CSDN博客保存为PDF<i class="fas fa-chevron-right" aria-hidden="true"></i></p>
          <p class='content'>F12打开开发者工具，在Console输入以下js代码：
123456789101112131415161718(function()&#123;	&#x27;use strict&#x27;;...</p>
        </a>
      
    </div>
  
</article>


  

  






</div>
<aside class='l_side'>
  
  
    
    



  <section class="widget toc-wrapper shadow floatable desktop mobile" id="toc-div" >
    
  <header>
    
      <i class="fas fa-list fa-fw" aria-hidden="true"></i><span class='name'>本文目录</span>
    
  </header>


    <div class='content'>
        <ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#5-1-SGX%E7%89%A9%E7%90%86%E5%86%85%E5%AD%98%E7%BB%84%E7%BB%87"><span class="toc-text">5.1 SGX物理内存组织</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#5-1-1-Enclave%E9%A1%B5%E9%9D%A2%E7%BC%93%E5%AD%98-EPC"><span class="toc-text">5.1.1 Enclave页面缓存 (EPC)</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-1-2-Enclave%E9%A1%B5%E9%9D%A2%E7%BC%93%E5%AD%98%E6%98%A0%E5%B0%84%E8%A1%A8-EPCM"><span class="toc-text">5.1.2 Enclave页面缓存映射表 (EPCM)</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-1-3-SGX-Enclave%E6%8E%A7%E5%88%B6%E7%BB%93%E6%9E%84-SECS"><span class="toc-text">5.1.3 SGX Enclave控制结构 (SECS)</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5-2-SGX-Enclave%E7%9A%84%E5%86%85%E5%AD%98%E5%B8%83%E5%B1%80"><span class="toc-text">5.2 SGX Enclave的内存布局</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#5-2-1-Enclave%E7%BA%BF%E6%80%A7%E5%9C%B0%E5%9D%80%E8%8C%83%E5%9B%B4-ELRANGE"><span class="toc-text">5.2.1 Enclave线性地址范围 (ELRANGE)</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-2-2-SGX-Enclave%E5%B1%9E%E6%80%A7"><span class="toc-text">5.2.2 SGX Enclave属性</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-2-3-SGX-Enclaves%E5%9C%B0%E5%9D%80%E8%BD%AC%E6%8D%A2"><span class="toc-text">5.2.3 SGX Enclaves地址转换</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-2-4-%E7%BA%BF%E7%A8%8B%E6%8E%A7%E5%88%B6%E7%BB%93%E6%9E%84-TCS"><span class="toc-text">5.2.4 线程控制结构 (TCS)</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-2-5-%E7%8A%B6%E6%80%81%E4%BF%9D%E5%AD%98%E5%8C%BA%E5%9F%9F-SSA"><span class="toc-text">5.2.5 状态保存区域 (SSA)</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5-3-SGX-Enclave%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F"><span class="toc-text">5.3 SGX Enclave的生命周期</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#5-3-1-%E5%88%9B%E5%BB%BA"><span class="toc-text">5.3.1 创建</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-3-2-%E5%8A%A0%E8%BD%BD"><span class="toc-text">5.3.2 加载</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-3-3-%E5%88%9D%E5%A7%8B%E5%8C%96"><span class="toc-text">5.3.3 初始化</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-3-4-%E9%94%80%E6%AF%81"><span class="toc-text">5.3.4 销毁</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5-4-SGX%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F"><span class="toc-text">5.4 SGX线程的生命周期</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#5-4-1-%E5%90%8C%E6%AD%A5Enclave%E8%BF%9B%E5%85%A5"><span class="toc-text">5.4.1 同步Enclave进入</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-4-2-%E5%90%8C%E6%AD%A5Enclave%E9%80%80%E5%87%BA"><span class="toc-text">5.4.2 同步Enclave退出</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-4-3-%E5%BC%82%E6%AD%A5Enclave%E9%80%80%E5%87%BA"><span class="toc-text">5.4.3 异步Enclave退出</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-4-4-%E4%BB%8E%E5%BC%82%E6%AD%A5%E9%80%80%E5%87%BA%E6%81%A2%E5%A4%8D"><span class="toc-text">5.4.4 从异步退出恢复</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5-5-EPC%E9%A1%B5%E9%9D%A2%E4%BA%A4%E6%8D%A2"><span class="toc-text">5.5 EPC页面交换</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#5-5-1-%E9%A1%B5%E9%9D%A2%E4%BA%A4%E6%8D%A2%E5%92%8CTLB"><span class="toc-text">5.5.1 页面交换和TLB</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-5-2-%E7%89%88%E6%9C%AC%E6%95%B0%E7%BB%84-VA"><span class="toc-text">5.5.2 版本数组 (VA)</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-5-3-Enclave-IDs"><span class="toc-text">5.5.3 Enclave IDs</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-5-4-EPC%E9%A1%B5%E9%9D%A2%E4%BA%A4%E6%8D%A2"><span class="toc-text">5.5.4 EPC页面交换</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-5-5-%E5%B0%86%E4%BA%A4%E6%8D%A2%E7%9A%84%E9%A1%B5%E9%9D%A2%E5%8A%A0%E8%BD%BD%E5%9B%9EEPC"><span class="toc-text">5.5.5 将交换的页面加载回EPC</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-5-6-%E4%BA%A4%E6%8D%A2%E6%A0%91"><span class="toc-text">5.5.6 交换树</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5-6-SGX-Enclave%E5%BA%A6%E9%87%8F"><span class="toc-text">5.6 SGX Enclave度量</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#5-6-1-ECREATE%E5%BA%A6%E9%87%8F"><span class="toc-text">5.6.1 ECREATE度量</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-6-2-%E5%BA%A6%E9%87%8FEnclave%E5%B1%9E%E6%80%A7"><span class="toc-text">5.6.2 度量Enclave属性</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-6-3-%E5%BA%A6%E9%87%8FEADD"><span class="toc-text">5.6.3 度量EADD</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-6-4-%E5%BA%A6%E9%87%8FEEXTEND"><span class="toc-text">5.6.4 度量EEXTEND</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-6-5-%E5%BA%A6%E9%87%8FEINIT"><span class="toc-text">5.6.5 度量EINIT</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5-7-SGX-Enclave%E7%89%88%E6%9C%AC%E6%94%AF%E6%8C%81"><span class="toc-text">5.7 SGX Enclave版本支持</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#5-7-1-Enclave%E8%AF%81%E4%B9%A6"><span class="toc-text">5.7.1 Enclave证书</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-7-2-%E5%9F%BA%E4%BA%8E%E8%AF%81%E4%B9%A6%E7%9A%84Enclave%E8%BA%AB%E4%BB%BD"><span class="toc-text">5.7.2 基于证书的Enclave身份</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-7-3-CPU%E5%AE%89%E5%85%A8%E7%89%88%E6%9C%AC%E5%8F%B7"><span class="toc-text">5.7.3 CPU安全版本号</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-7-4-%E5%BB%BA%E7%AB%8BEnclave%E8%BA%AB%E4%BB%BD"><span class="toc-text">5.7.4 建立Enclave身份</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-7-5-Enclave%E5%AF%86%E9%92%A5%E5%88%86%E5%8F%91"><span class="toc-text">5.7.5 Enclave密钥分发</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5-8-SGX%E8%BD%AF%E4%BB%B6%E8%AE%A4%E8%AF%81"><span class="toc-text">5.8 SGX软件认证</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#5-8-1-%E6%9C%AC%E5%9C%B0%E8%AE%A4%E8%AF%81"><span class="toc-text">5.8.1 本地认证</span></a></li></ol></li></ol>
    </div>
  </section>


  


</aside>



        
        
          <!--此文件用来存放一些不方便取值的变量-->
<!--思路大概是将值藏到重加载的区域内-->

<script>
  window.pdata={}
  pdata.ispage=true;
  pdata.postTitle="翻译-SGX Programming Model";
  pdata.commentPath="";
  pdata.commentPlaceholder="";

  var l_header=document.getElementById("l_header");
  
  l_header.classList.add("show");
  
</script>

        
      </div>
      
  
  <footer class="footer clearfix">
    <br><br>
    
      
        <div class='copyright'>
        <p><a target="_blank" rel="noopener" href="https://github.com/Schenk75/Schenk75.github.io">Copyright © 2020-2022 Schenk</a></p>

        </div>
      
    
  </footer>


      <a id="s-top" class="fas fa-arrow-up fa-fw" href='javascript:void(0)'></a>
    </div>
  </div>
  <div>
    <script>
window.volantis={};
window.volantis.loadcss=document.getElementById("loadcss");
/********************脚本懒加载函数********************************/
function loadScript(src, cb) {
var HEAD = document.getElementsByTagName('head')[0] || document.documentElement;
var script = document.createElement('script');
script.setAttribute('type','text/javascript');
if (cb) script.onload = cb;
script.setAttribute('src', src);
HEAD.appendChild(script);
}
//https://github.com/filamentgroup/loadCSS
var loadCSS = function( href, before, media, attributes ){
	var doc = window.document;
	var ss = doc.createElement( "link" );
	var ref;
	if( before ){
		ref = before;
	}
	else {
		var refs = ( doc.body || doc.getElementsByTagName( "head" )[ 0 ] ).childNodes;
		ref = refs[ refs.length - 1];
	}
	var sheets = doc.styleSheets;
	if( attributes ){
		for( var attributeName in attributes ){
			if( attributes.hasOwnProperty( attributeName ) ){
				ss.setAttribute( attributeName, attributes[attributeName] );
			}
		}
	}
	ss.rel = "stylesheet";
	ss.href = href;
	ss.media = "only x";
	function ready( cb ){
		if( doc.body ){
			return cb();
		}
		setTimeout(function(){
			ready( cb );
		});
	}
	ready( function(){
		ref.parentNode.insertBefore( ss, ( before ? ref : ref.nextSibling ) );
	});
	var onloadcssdefined = function( cb ){
		var resolvedHref = ss.href;
		var i = sheets.length;
		while( i-- ){
			if( sheets[ i ].href === resolvedHref ){
				return cb();
			}
		}
		setTimeout(function() {
			onloadcssdefined( cb );
		});
	};
	function loadCB(){
		if( ss.addEventListener ){
			ss.removeEventListener( "load", loadCB );
		}
		ss.media = media || "all";
	}
	if( ss.addEventListener ){
		ss.addEventListener( "load", loadCB);
	}
	ss.onloadcssdefined = onloadcssdefined;
	onloadcssdefined( loadCB );
	return ss;
};
</script>
<script>
  
  loadCSS("https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.14/css/all.min.css", window.volantis.loadcss);
  
  
  
  
</script>
<!-- required -->

<script src="https://cdn.jsdelivr.net/npm/jquery@3.5/dist/jquery.min.js"></script>

<script>
  function pjax_fancybox() {
    $(".md .gallery").find("img").each(function () { //渲染 fancybox
      var element = document.createElement("a"); // a 标签
      $(element).attr("class", "fancybox");
      $(element).attr("pjax-fancybox", "");  // 过滤 pjax
      $(element).attr("href", $(this).attr("src"));
      if ($(this).attr("data-original")) {
        $(element).attr("href", $(this).attr("data-original"));
      }
      $(element).attr("data-fancybox", "images");
      var caption = "";   // 描述信息
      if ($(this).attr('alt')) {  // 判断当前页面是否存在描述信息
        $(element).attr('data-caption', $(this).attr('alt'));
        caption = $(this).attr('alt');
      }
      var div = document.createElement("div");
      $(div).addClass("fancybox");
      $(this).wrap(div); // 最外层套 div ，其实主要作用还是 class 样式
      var span = document.createElement("span");
      $(span).addClass("image-caption");
      $(span).text(caption); // 加描述
      $(this).after(span);  // 再套一层描述
      $(this).wrap(element);  // 最后套 a 标签
    })
    $(".md .gallery").find("img").fancybox({
      selector: '[data-fancybox="images"]',
      hash: false,
      loop: false,
      closeClick: true,
      helpers: {
        overlay: {closeClick: true}
      },
      buttons: [
        "zoom",
        "close"
      ]
    });
  };
  function SCload_fancybox() {
    if ($(".md .gallery").find("img").length == 0) return;
    loadCSS("https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css", document.getElementById("loadcss"));
    setTimeout(function() {
      loadScript('https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js', pjax_fancybox)
    }, 1);
  };
  $(function () {
    SCload_fancybox();
  });
</script>


<!-- internal -->







  <script defer src="https://cdn.jsdelivr.net/npm/vanilla-lazyload@17.1.0/dist/lazyload.min.js"></script>
<script>
  // https://www.npmjs.com/package/vanilla-lazyload
  // Set the options globally
  // to make LazyLoad self-initialize
  window.lazyLoadOptions = {
    elements_selector: ".lazyload",
    threshold: 0
  };
  // Listen to the initialization event
  // and get the instance of LazyLoad
  window.addEventListener(
    "LazyLoad::Initialized",
    function (event) {
      window.lazyLoadInstance = event.detail.instance;
    },
    false
  );
  document.addEventListener('DOMContentLoaded', function () {
    lazyLoadInstance.update();
  });
  document.addEventListener('pjax:complete', function () {
    lazyLoadInstance.update();
  });
</script>




  
  
    <script>
      window.FPConfig = {
        delay: 0,
        ignoreKeywords: [],
        maxRPS: 5,
        hoverDelay: 25
      };
    </script>
    <script defer src="https://cdn.jsdelivr.net/gh/gijo-varghese/flying-pages@2.1.2/flying-pages.min.js"></script>
  




  <script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
    var clipboard = new ClipboardJS('.btn-copy', {
        target: function (trigger) {
            return trigger.nextElementSibling
        }
    });
    function wait(callback, seconds) {
        var timelag = null;
        timelag = window.setTimeout(callback, seconds)
    }
    function pjax_initCopyCode() {
		if($(".highlight .code pre").length+$(".article pre code").length==0)return;
        var copyHtml = '';
        copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
        copyHtml += '<i class="fas fa-copy"></i><span>COPY</span>';
        copyHtml += '</button>';
        $(".highlight .code pre").before(copyHtml);
        $(".article pre code").before(copyHtml);
        clipboard.off('success').on('success', function (e) {
            let $btn = $(e.trigger);
            $btn.addClass('copied');
            let $icon = $($btn.find('i'));
            $icon.removeClass('fa-copy');
            $icon.addClass('fa-check-circle');
            let $span = $($btn.find('span'));
            $span[0].innerText = 'COPIED';
            wait(function () {
                $icon.removeClass('fa-check-circle');
                $icon.addClass('fa-copy');
                $span[0].innerText = 'COPY'
            }, 2000)
        });
        clipboard.off('error').on('error', function (e) {
            e.clearSelection();
            let $btn = $(e.trigger);
            $btn.addClass('copy-failed');
            let $icon = $($btn.find('i'));
            $icon.removeClass('fa-copy');
            $icon.addClass('fa-times-circle');
            let $span = $($btn.find('span'));
            $span[0].innerText = 'COPY FAILED';
            wait(function () {
                $icon.removeClass('fa-times-circle');
                $icon.addClass('fa-copy');
                $span[0].innerText = 'COPY'
            }, 2000)
        })
    }
    $(function () {
        pjax_initCopyCode()
    });
</script>










  <script defer src="https://cdn.jsdelivr.net/gh/Schenk75/Source@master/tools/busuanzi.pure.mini.js" data-pjax></script>


  
<script src="https://cdn.jsdelivr.net/npm/hexo-theme-volantis@4.1.5/source/js/app.min.js"></script>




  
  
<script src="https://cdn.jsdelivr.net/npm/hexo-theme-volantis@4.1.5/source/js/search.min.js"></script>

  


<!-- optional -->

  <script>
const SearchServiceimagePath="https://cdn.jsdelivr.net/gh/volantis-x/cdn-volantis@master/img/";
const ROOT =  ("/" || "/").endsWith('/') ? ("/" || "/") : ("//" || "/" );
function listenSearch(){
  
    customSearch = new HexoSearch({
      imagePath: SearchServiceimagePath
    });
  
}
document.addEventListener("DOMContentLoaded", listenSearch);

</script>











  <script defer>

  const LCCounter = {
    app_id: 'u9j57bwJod4EDmXWdxrwuqQT-MdYXbMMI',
    app_key: 'jfHtEKVE24j0IVCGHbvuFClp',
    custom_api_server: '',

    // 查询存储的记录
    getRecord(Counter, url, title) {
      return new Promise(function (resolve, reject) {
        Counter('get', '/classes/Counter?where=' + encodeURIComponent(JSON.stringify({url})))
          .then(resp => resp.json())
          .then(({results, code, error}) => {
            if (code === 401) {
              throw error;
            }
            if (results && results.length > 0) {
              var record = results[0];
              resolve(record);
            } else {
              Counter('post', '/classes/Counter', {url, title: title, times: 0})
                .then(resp => resp.json())
                .then((record, error) => {
                  if (error) {
                    throw error;
                  }
                  resolve(record);
                }).catch(error => {
                console.error('Failed to create', error);
                reject(error);
              });
            }
          }).catch((error) => {
          console.error('LeanCloud Counter Error:', error);
          reject(error);
        });
      })
    },

    // 发起自增请求
    increment(Counter, incrArr) {
      return new Promise(function (resolve, reject) {
        Counter('post', '/batch', {
          "requests": incrArr
        }).then((res) => {
          res = res.json();
          if (res.error) {
            throw res.error;
          }
          resolve(res);
        }).catch((error) => {
          console.error('Failed to save visitor count', error);
          reject(error);
        });
      });
    },

    // 构建自增请求体
    buildIncrement(objectId) {
      return {
        "method": "PUT",
        "path": `/1.1/classes/Counter/${ objectId }`,
        "body": {
          "times": {
            '__op': 'Increment',
            'amount': 1
          }
        }
      }
    },

    // 校验是否为有效的 UV
    validUV() {
      var key = 'LeanCloudUVTimestamp';
      var flag = localStorage.getItem(key);
      if (flag) {
        // 距离标记小于 24 小时则不计为 UV
        if (new Date().getTime() - parseInt(flag) <= 86400000) {
          return false;
        }
      }
      localStorage.setItem(key, new Date().getTime().toString());
      return true;
    },

    addCount(Counter) {
      var enableIncr = '' === 'true' && window.location.hostname !== 'localhost';
      enableIncr = true;
      var getterArr = [];
      var incrArr = [];
      // 请求 PV 并自增
      var pvCtn = document.querySelector('#lc-sv');
      if (pvCtn || enableIncr) {
        var pvGetter = this.getRecord(Counter, 'http://example.com' + '/#lc-sv', 'Visits').then((record) => {
          incrArr.push(this.buildIncrement(record.objectId))
          var eles = document.querySelectorAll('#lc-sv #number');
          if (eles.length > 0) {
            eles.forEach((el,index,array)=>{
              el.innerText = record.times + 1;
              if (pvCtn) {
                pvCtn.style.display = 'inline';
              }
            })
          }
        });
        getterArr.push(pvGetter);
      }

      // 请求 UV 并自增
      var uvCtn = document.querySelector('#lc-uv');
      if (uvCtn || enableIncr) {
        var uvGetter = this.getRecord(Counter, 'http://example.com' + '/#lc-uv', 'Visitors').then((record) => {
          var vuv = this.validUV();
          vuv && incrArr.push(this.buildIncrement(record.objectId))
          var eles = document.querySelectorAll('#lc-uv #number');
          if (eles.length > 0) {
            eles.forEach((el,index,array)=>{
              el.innerText = record.times + (vuv ? 1 : 0);
              if (uvCtn) {
                uvCtn.style.display = 'inline';
              }
            })
          }
        });
        getterArr.push(uvGetter);
      }

      // 请求文章的浏览数，如果是当前页面就自增
      var allPV = document.querySelectorAll('#lc-pv');
      if (allPV.length > 0 || enableIncr) {
        for (i = 0; i < allPV.length; i++) {
          let pv = allPV[i];
          let title = pv.getAttribute('data-title');
          var url = 'http://example.com' + pv.getAttribute('data-path');
          if (url) {
            var viewGetter = this.getRecord(Counter, url, title).then((record) => {
              // 是当前页面就自增
              let curPath = window.location.pathname;
              if (curPath.includes('index.html')) {
                curPath = curPath.substring(0, curPath.lastIndexOf('index.html'));
              }
              if (pv.getAttribute('data-path') == curPath) {
                incrArr.push(this.buildIncrement(record.objectId));
              }
              if (pv) {
                var ele = pv.querySelector('#lc-pv #number');
                if (ele) {
                  if (pv.getAttribute('data-path') == curPath) {
                    ele.innerText = (record.times || 0) + 1;
                  } else {
                    ele.innerText = record.times || 0;
                  }
                  pv.style.display = 'inline';
                }
              }
            });
            getterArr.push(viewGetter);
          }
        }
      }

      // 如果启动计数自增，批量发起自增请求
      if (enableIncr) {
        Promise.all(getterArr).then(() => {
          incrArr.length > 0 && this.increment(Counter, incrArr);
        })
      }

    },


    fetchData(api_server) {
      var Counter = (method, url, data) => {
        return fetch(`${ api_server }/1.1${ url }`, {
          method,
          headers: {
            'X-LC-Id': this.app_id,
            'X-LC-Key': this.app_key,
            'Content-Type': 'application/json',
          },
          body: JSON.stringify(data)
        });
      };
      this.addCount(Counter);
    },

    refreshCounter() {
      var api_server = this.app_id.slice(-9) !== '-MdYXbMMI' ? this.custom_api_server : `https://${ this.app_id.slice(0, 8).toLowerCase() }.api.lncldglobal.com`;
      if (api_server) {
        this.fetchData(api_server);
      } else {
        fetch('https://app-router.leancloud.cn/2/route?appId=' + this.app_id)
          .then(resp => resp.json())
          .then(({api_server}) => {
            this.fetchData('https://' + api_server);
          });
      }
    }

  };

  LCCounter.refreshCounter();

  document.addEventListener('pjax:complete', function () {
    LCCounter.refreshCounter();
  });
</script>








<script>
function listennSidebarTOC() {
  const navItems = document.querySelectorAll(".toc li");
  if (!navItems.length) return;
  const sections = [...navItems].map((element) => {
    const link = element.querySelector(".toc-link");
    const target = document.getElementById(
      decodeURI(link.getAttribute("href")).replace("#", "")
    );
    link.addEventListener("click", (event) => {
      event.preventDefault();
      window.scrollTo({
		top: target.offsetTop + 100,
		
		behavior: "smooth"
		
	  });
    });
    return target;
  });

  function activateNavByIndex(target) {
    if (target.classList.contains("active-current")) return;

    document.querySelectorAll(".toc .active").forEach((element) => {
      element.classList.remove("active", "active-current");
    });
    target.classList.add("active", "active-current");
    let parent = target.parentNode;
    while (!parent.matches(".toc")) {
      if (parent.matches("li")) parent.classList.add("active");
      parent = parent.parentNode;
    }
  }

  function findIndex(entries) {
    let index = 0;
    let entry = entries[index];
    if (entry.boundingClientRect.top > 0) {
      index = sections.indexOf(entry.target);
      return index === 0 ? 0 : index - 1;
    }
    for (; index < entries.length; index++) {
      if (entries[index].boundingClientRect.top <= 0) {
        entry = entries[index];
      } else {
        return sections.indexOf(entry.target);
      }
    }
    return sections.indexOf(entry.target);
  }

  function createIntersectionObserver(marginTop) {
    marginTop = Math.floor(marginTop + 10000);
    let intersectionObserver = new IntersectionObserver(
      (entries, observe) => {
        let scrollHeight = document.documentElement.scrollHeight + 100;
        if (scrollHeight > marginTop) {
          observe.disconnect();
          createIntersectionObserver(scrollHeight);
          return;
        }
        let index = findIndex(entries);
        activateNavByIndex(navItems[index]);
      },
      {
        rootMargin: marginTop + "px 0px -100% 0px",
        threshold: 0,
      }
    );
    sections.forEach((element) => {
      element && intersectionObserver.observe(element);
    });
  }
  createIntersectionObserver(document.documentElement.scrollHeight);
}

document.addEventListener("DOMContentLoaded", listennSidebarTOC);
document.addEventListener("pjax:success", listennSidebarTOC);
</script>

<!-- more -->




    
      


<script src="https://cdn.jsdelivr.net/npm/pjax@0.2.8/pjax.min.js"></script>


<script>
    var pjax;
    document.addEventListener('DOMContentLoaded', function () {
      pjax = new Pjax({
        elements: 'a[href]:not([href^="#"]):not([href="javascript:void(0)"]):not([pjax-fancybox])',
        selectors: [
          "title",
          "#l_cover",
          "#pjax-container",
          "#pjax-header-nav-list"
        ],
        cacheBust: false,   // url 地址追加时间戳，用以避免浏览器缓存
        timeout: 5000
      });
    });

    document.addEventListener('pjax:send', function (e) {
      //window.stop(); // 相当于点击了浏览器的停止按钮

      try {
        var currentUrl = window.location.pathname;
        var targetUrl = e.triggerElement.href;
        var banUrl = [""];
        if (banUrl[0] != "") {
          banUrl.forEach(item => {
            if(currentUrl.indexOf(item) != -1 || targetUrl.indexOf(item) != -1) {
              window.location.href = targetUrl;
            }
          });
        }
      } catch (error) {}

      window.subData = null; // 移除标题（用于一二级导航栏切换处）
      if (typeof $.fancybox != "undefined") {
        $.fancybox.close();    // 关闭弹窗
      }
      volantis.$switcher.removeClass('active'); // 关闭移动端激活的搜索框
      volantis.$header.removeClass('z_search-open'); // 关闭移动端激活的搜索框
      volantis.$wrapper.removeClass('sub'); // 跳转页面时关闭二级导航

      // 解绑事件 避免重复监听
      volantis.$topBtn.unbind('click');
      $('.menu a').unbind('click');
      $(window).unbind('resize');
      $(window).unbind('scroll');
      $(document).unbind('scroll');
      $(document).unbind('click');
      $('body').unbind('click');
	  
    });

    document.addEventListener('pjax:complete', function () {
      // 关于百度统计对 SPA 页面的处理：
      // 方案一：百度统计>管理>单页应用设置中，打开开启按钮即可对SPA进行统计。 https://tongji.baidu.com/web/help/article?id=324
      // 方案二：取消注释下列代码。 https://tongji.baidu.com/web/help/article?id=235
      // 

      // 关于谷歌统计对 SPA 页面的处理：
      // 当应用以动态方式加载内容并更新地址栏中的网址时，也应该更新通过 gtag.js 存储的网页网址。
      // https://developers.google.cn/analytics/devguides/collection/gtagjs/single-page-applications?hl=zh-cn
      

      $('.nav-main').find('.list-v').not('.menu-phone').removeAttr("style",""); // 移除小尾巴的移除
      $('.menu-phone.list-v').removeAttr("style",""); // 移除小尾巴的移除
      $('script[data-pjax], .pjax-reload script').each(function () {
        $(this).parent().append($(this).remove());
      });
      try{
          if (typeof $.fancybox == "undefined") {
            SCload_fancybox();
          } else {
            pjax_fancybox();
          }
        
        
        
        
        
          pjax_initCopyCode();
        
        
        
        
        
      } catch (e) {
        console.log(e);
      }
	  
    });

    document.addEventListener('pjax:error', function (e) {
	  
      window.location.href = e.triggerElement.href;
    });
</script>

    
  </div>
</body>
</html>
